FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

@@ -0,0 +1,151 @@
Since 7.x-1.0
Performance improvements.
Major restructure of admin UI.
Workflow form detached from comment form.
Workflow status and form are fields that can be controlled under "Manage display."
Issue #1783854 by antojose: Allow setting comment in Rules.
Issue #1891446 by interX: Allow grouped filters.
Issue #1922262 by NancyDru, hwold: Fix join. misc. improvements.
Issue #1540824 by NancyDru: Improve Admin UI.
Issue #1908520 by NancyDru: Set state of pre-existing nodes.
Issue #1922422 by NancyDru: Correct state name creation in Tokens.
Issue #155547 by NancyDru: Tab form should call hook_workflow().
Issue #1818424 by NancyDru: Add doc for hook_workflow_operations.
Issue #1918424 by NancyDru: More complete api doc.
Get rid of temp.patch.
Issue #385038 by flyingkiki, NancyDru: Fix double history. Minor corrections.
Issue #1802216 by NancyDru: Improve scheduling time form section.
Issue #1418622 by NancyDru: Fix tokens during transition.
Issue #1784092 by wamilton: stop double saving nodes.
remove accidental file
Issue #1900488 by NancyDru: load comment in node_load. #1904740 by NancyDru: suppress form at terminal state. Performance improvements.
Issue #1532646 by wonder95: Add search api module.
Issue #1493012 by kid_icarus: Better views.
Issue #1900480 by NancyDru for Dave Reid: Move tokens stuff to separate file.
Issue #1418622 by Dave Reid: Fix tokens.
Issue #1427006 by NancyDru: Fix transition rule.
Issue #1893542 by NancyDru: Transition form by perms.
Issue #1893724 by NancyDru: transition form by perms.
Issue #1895712 by NancyDru: Fix form when only one state.
Issue #1896422 by NancyDru: Fix token message.
Correct theming in form.
Minor corrections to README
Issue #1856180 by NancyDru: Correct update to not fail.
Issue #1475930 by Bastlynn.
Issue #1874400 by Nancydru.
Issue #1884630 by NancyDru.
Issue #1891374 by NancyDru.
Coder finds.
Issue #1550274 by dynamicdan, gofs: egregious typo
Adding features include on revert so it's avail during install / enable.
UI improvement in Rules interface when there are more than one Workflows available.
Issue #1471014 by Bastlynn: removing node_save from workflow tab.
Issue #558378 by hefox: Features round 2 cleanup.
Issue #1426844 by onegenius: Correcting for missing history information errs.
Issue #1424008 by Bastlynn: handling no workflows setup w/ Workflow Views turned on.
Issue #1421518 by DuaelFr: Workflow access warnings on new nodes
Issue #1468810 by firewolf, Morten Najbjerg, sbrege Strict warnings re: render and form
Issue #1469798 by Bastlynn: errs thrown on rules change when old state = new state.
Issue #1468810 Timezones in scheduler
Issue #1426844 by Bastlynn: Adding catch re: propery of last_history object being empty when node is not a workflow content type.
Issue #1424008 by Bastlynn: Accounting for trying to go to views pg with workflow_views active, but no workflows in system.
Issue #1400352 by Bastlynn: Fixing an error appearing udner E_STRICT in admin UI re: creating a default object.
Something got wierd betwen the push/pull for this commit so trying that again.
Issue #1386430 by dylanhuang (patch by dozymoe): Views filter shows unknown when state selected in multi-workflow environment.
Issue #1386430 by dylanhuang (patch by dozymoe): Views filter shows unknown when state selected in multi-workflow environment.
Correcting for doubled ,, in install schema.
Issue #1405688 by wonder95: Path length correction for trigger path.
Issue #1405688 by hefox: Coder standards.
Issue #1924004 by evaldas.uzkuras: Fix bad variable.
Fix typo in api. Add hook_requirements to show that worflows are active.
Issue #1924174 by NancyDru: Fix menu error on state delete.
Issue 1925162 by fgm: Call time reference error.
Issue #1924182 by NancyDru: Correct initial state name in edit form.
Issue #1926746 by shenzhuxi: Fix some php warnings.
Issue #1781308 by NancyDru: Change node form to vertical tab.
Issue #1926556 by NancyDru: Back to transition on node creation.
Issue #1550992 by chadhester: Add Previous Comment Author for Views.
Issue #1926800 by NancyDru: Fix operations links.
Issue #1865754 by NancyDru for leelooch: Add reminder to state add form.
Issue #1540412 by NancyDru: Allow timezones for transition scheduling.
Issue #1930638 by NancyDru, shenzhuxi: Undefined variable.
Issue #1540824 by NancyDru: Improve Admin UI.
Issue #1931032 by NancyDru: Add missing js file.
Issue #1931778 by NancyDru: Add breadcrumbs to access page.
Add admin ui css.
Issue #1350900 by loze: list states grouped by workflow.
Issue #1540824 by NancyDru: More Admin UI improvements.
Issue #1931948 by NancyDru: Use menu loaders to simplify menu.
Issue #1347116 by NancyDru: New Workflow_Cleanup module.
Issue #1509568 by fgm: Access control export.
Issue #1933466 by fgm: Remove Admin UI warning, repair redirection on workflow_access admin submit.
Issue #1768350 by NancyDru: Correct test and message for type page.
Issue #1380954 by Bastlyn, DRippstein: Add node_actions triggers.
Issue #1909922 by NancyDru: Add warning on Access UI page about content permissions.
Issue #1691870 by berenddeboer: Allow Rules to bypass permissions on transition.
Issue #1540914 by NancyDru: Pass parms in node form; allow comment alter at transition.
Issue #1744612 by NancyDru: Move VBO stuff into its own module.
Issue #1944574 by NancyDru: Undefined index.
Since 7.x-1.1-rc1
Issue #785194 by arcovia: Access to tab form.
Issue #785194 by NancyDru: Better access to tab form.
Issue #1691870 by NancyDru: Rules record workflow comment.
Issue #1691870 by NancyDru: Transition check wrong choices on Force.
Issue #1951164 by Kristen Pol: Fix left join order.
Issue #1951718 by NancyDru: Clean up history records.
Issue #1953642 by NancyDru: Add history row alter feature.
Issue #1953642 by NancyDru: Add state revert feature.
Issue #1953642 by NancyDru: Document hook_workflow_history_alter().
Issue #1484126 by NancyDru, nathangervais: Check if node type has a workflow before adding WF info.
Issue #1957074 by andypost: Allow Views to show Sid value.
Address some @TODO items.
Since 7.x-1.1
Issue #1961156 by mErilainen: Missing translate in Views.
Issue #1962344 by NancyDru, manu77: Missing $node->workflow.
Issue #1962512 by NancyDru: Don't let (creation) be changed.
Issue #1961426 by andypost: Fix reference error.
Issue #1893420 by JackVVo: Fix node name.
Issue #1966292 by aBrookland: Undefined index.
Issue #1970538 by Tim Asplin: Improve Features support.
Issue #1589254 by DuaelFr: Improve Features support.
Issue #1972728 by NancyDru: Deal with HTML entities and translations.
Issue #1967794 by NancyDru, dforegger: Remove static cache.
Issue #1966804 by andypost: Improve workflow_get_workflow_node_history_by_nid.
Issue #1970846 by NancyDru: Allow multiple workflow_tab_forms on the same page.
Issue #1974828 by martysteer: Fix missing brackets in query.
Replace all SELECT * with column names. (Remove @TODO)
Issue #1976992 by bdone, NancyDru: Make watchdog logging optional.
Issue #1976942 by dforegger: Fix transition import.
Issue #1984338 by NancyDru: Fix node_load looping.
Issue #1995004 by NancyDru: Fix form issue in admin.
Issue #1975058 by NancyDru: Revert change to node_form on comments.
Issue #1986220 by NancyDru: Add scheduling user's id to history.
Automatically give "Participate in Workflow" permission to new roles.
Issue #1984338 by NancyDru: Pass $force to hook_workflow().
Issue #1996892 by Kristen Pol: Add previous state to Rules.
Issue #1979562 by NancyDru: workflow_tab_form not found.
Issue #1974624 by NancyDru: Make sure all hook calls have two params.
Issue #1971504 by NancyDru: Skip node form vertical tab if only one transition possible.
Issue #1997242 by NancyDru: Hook to check if transition is allowed.
Issue #1443166 by NancyDru: Clean up monster query (Oracle issue).
Issue #2001980 by NancyDru: Revert checks if permitted.
Issue #1856180 by JayMN: Correct typo in update.
Issue #2009312 by NancyDru: Remove Workflow module form field.
Issue #457348 by NancyDru: Remove view mode check.
Fix terminal state check.
Issue #1997242 by NancyDru: Recheck permitted transitions before execution.
Issue #1781308 by NancyDru: Revert change to vertical tab.
Issue #1997242 by Tim-Erwin: Honor force on transition permitted.
Issue #2019125 by Nancydru: Add access priority setting.
Issue #2018959 by dev team data: Fix feature revert error.
Issue #2012516 by maximpodorov: Add state system name and sid to theme functions
Issue #2022381 by justanothermark: Fix array key collision
Issue #2022333 by NancyDru: Add another common permission warning.
Issue #2023233 by NancyDru: Fix double encoding.
Issue #1993408 by NancyDru: Default new states to active.
Issue #1542188 by justanothermark: Fix workflow display.
Issue #2027507 by NancyDru: Fix sorting issue on history page.
Issue #1993408 by NancyDru: Sysid not always set correctly.

View File

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

View File

@@ -0,0 +1,113 @@
********************************************************************
D R U P A L M O D U L E
********************************************************************
Name: Workflow Module
Author: John VanDyk
Maintainers: Mark Fredrickson <mark.m.fredrickson at gmail dot com>
John VanDyk drupal.org/user/2375
Bastlynn http://drupal.org/user/275249
Nancy Wichmann (NancyDru) http://drupal.org/user/101412
Drupal: 7
********************************************************************
DESCRIPTION:
The workflow module enables you to create arbitrary workflows in
Drupal and associate them with node types.
Workflows are made up of workflow states.
Moving from one state to another is called a transition.
Actions are associated with transitions (actions.module was used
for this in Drupal 5; core actions support is in Drupal 6).
Alex Reisner introduced role-based permissions for workflow states
and generally enhanced this module.
********************************************************************
INSTALLATION:
1. Place the entire workflow directory into your Drupal
sites/all/modules directory (or appropriate alternative).
2. Enable the workflow module by navigating to:
Administer > Site building > Modules
Enabling the workflow module will create the necessary database
tables for you.
3. If you wish to use the administrative UI, then enable the
Workflow UI module. There are several other optional modules
that you may also enable, if needed.
4. If you want anyone besides the administrative user to be able
to configure workflows (usually a bad idea), they must be given
the "administer workflow" access permission:
Administer > User management > Permissions
When the module is enabled and the user has the "administer
workflow" permission, a "Workflow" menu should appear in the
menu system under Administer -> Site building.
You may also allow only some users to schedule transitions. Select
the "schedule workflow transitions" permission to allow transitions.
********************************************************************
GETTING STARTED:
Let's create a new workflow. Click on Administer -> Configuration ->
Workflow -> Workflow and click on the "Add workflow" tab.
We'll start simple. Call our workflow "Draft-Done" and click Add Workflow.
Now lets add some workflow states to our workflow. Click "add state" and
enter "draft" and click the Add State button. Do the same for "done".
So we've got a workflow with two states, "draft" and "done". Now we
have to tell each state which other states it can move to. With only
two states, this is easy. Click on the "edit" link to edit the workflow
and see its states.
The "From / To -->" column lists all states. To the right are columns
for each state. Within each cell is a list of roles with checkboxes.
This is confusing. It's easiest to understand if you read rows
across from the left. For example, we start with the creation
state. Who may move a node from its creation state to the "draft"
state? Well, the author of the node, for one. So check the "author"
checkbox.
Who may move the node from the "draft" state to the "done" state?
This is up to you. If you want authors to be able to do this,
check the "author" checkbox under the "done" state. If you had
another role, say "editor", that you wanted to give the ability
to decree a node as "done", you'd check the checkbox next to
the "editor" role and not the author role. In this scenario authors
would turn in drafts and editors would say when they are "done".
Be sure to click the Save button to save your settings.
Now let's tell Drupal which node types should use this workflow. Click
on Administer -> Configuration -> Workflow -> Workflow. Let's assign
the Draft-Done workflow to the article node type and click Save Workflow
Mapping.
Now we could add an action (previously configured using the trigger
module). Click on the Actions link above
your workflow. Add the action to the transition.
Now create a new article by going to Create content -> article. If there
is no sign of a workflow interface here, don't panic. The interface
is only displayed if there is more than one state to which the user
can move the node (why bother the user with a form with only one
selection?) Click Submit to create the article.
You can see the state the node is in and the history of state changes
by clicking on the Workflow tab while viewing a node.
Changing the state to "done" and clicking Submit will fire the action
you set up earlier.
********************************************************************

View File

@@ -0,0 +1,39 @@
CONTENTS OF THIS FILE
---------------------
* Update from Drupal 6 to Drupal 7 Instructions
* Update Rules that use Workflow
UPDATE FROM DRUPAL 6 TO DRUPAL 7 INSTRUCTIONS
------------
Upgrading from 6 to 7 for Workflow using this module is fairly simple. The tables and formats of Drupal 6 configurations have not been changed, nor have the names of tokens or of Views filters and plugins.
The only major change in the Drupal 7 update was to the Rules integration of Workflow. The following steps should lead you through the process:
1) Follow the standard process of updating core from Drupal 6 to Drupal 7. This includes making sure you are on the latest version core for Drupal 6 and latest versions of all Drupal 6 modules. Turn off all contributed modules. Make backups of all files and database tables and run update.php. (See the directions at drupal.org for a more in-depth approach to this step.)
2) Remove the previous version of the Workflow module from your modules directory.
3) Unzip the updated version of Workflow and replace the old module.
4) Turn your modules back on and run update.php.
At this point your Workflows should be in place on your site. Assuming you are making use of the additional modules, your views will be using the updated View integrations for Workflow and Workflow Tokens will be in place.
UPDATE RULES THAT USE WORKFLOW
------------
Rules for Drupal 7 underwent major changes. Thankfully there is a built in update script for Rules to allow configurations created in previous versions of Rules to be updated and saved as new rules, but it is not perfect. To update your pre-existing Rules configurations that used Workflows:
1) Go to the Rules upgrade page at admin/config/workflow/rules/upgrade
2) Select the rule(s) you want to convert to the latest version of Rules
3) Under "Method" select "Convert configuration and save it". IMPORTANT: With the changes to Workflow's rules integration the configurations will not function properly on export / import. Save the configuration to the database to get around this.
4) IMPORTANT: Because the conversion is not perfect, check through each of your rules configurations for conditions and actions. You will need to change configurations for workflow states from "data selection" to "direct input" mode and re-configure the proper values to compare the given node against.
Updated to Drupal 7 by Bastlynn http://drupal.org/user/275249

View File

@@ -0,0 +1,63 @@
<?php
/**
* @file
* Hooks provided by the workflow module.
*/
/**
* Implements hook_workflow().
*
* @param $op
* The current workflow operation: 'transition permitted', 'transition pre' or 'transition post'.
* @param $old_state
* The state ID of the current state.
* @param $new_state
* The state ID of the new state.
* @param $node
* The node whose workflow state is changing.
* @param $force
* The caller indicated that the transition should be forced. (bool).
* This is only available on the "pre" and "post" calls.
*/
function hook_workflow($op, $old_state, $new_state, $node, $force = FALSE) {
switch ($op) {
case 'transition permitted':
// The workflow module does nothing during this operation.
// This operation occurs when the list of available transitions
// is built. Your module's implementation could return FALSE
// here and disallow the presentation of the choice.
break;
case 'transition pre':
// The workflow module does nothing during this operation.
// But your module's implementation of the workflow hook could
// return FALSE here and veto the transition.
break;
case 'transition post':
break;
case 'transition delete':
break;
}
}
/**
* Implements hook_workflow_history_alter().
* Add an 'undo' operation for the most recent history change.
*
* @param $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_node_history table.
*
* If you want to add additional data, such as an operation link,
* place it in the 'extra' value.
*/
function hook_workflow_history_alter(&$variables) {
// The Worflow module does nothing with this hook.
// For an example implementation, see the Workflow Revert add-on.
}

View File

@@ -0,0 +1,270 @@
<?php
/**
* @file
* Intergrates workflow with features.
*/
define('WORKFLOW_FEATURES_AUTHOR_NAME', 'workflow_features_author_name');
/**
* Workflows are a **faux-exportable** component.
*/
/**
* Implements hook_features_export().
*/
function workflow_features_export($data, &$export, $module_name = '') {
// fontyourface_default_fonts integration is provided by Features.
$export['dependencies']['features'] = 'features';
$export['dependencies']['workflow'] = 'workflow';
foreach (workflow_get_workflows() as $workflow) {
if (in_array($workflow->name, $data)) {
$export['features']['workflow'][$workflow->name] = $workflow->name;
}
}
return $export;
}
/**
* Implements hook_features_export_render().
*/
function workflow_features_export_render($module, $data) {
$translatables = $code = array();
$code[] = ' $workflows = array();';
$code[] = '';
$workflows = workflow_get_workflows();
foreach ($data as $name) {
if ($workflow = workflow_get_workflows_full_object($name)) {
unset($workflow->wid);
$workflow_export = features_var_export($workflow, ' ');
$workflow_identifier = features_var_export($workflow->name);
$code[] = " // Exported workflow: $name";
$code[] = " \$workflows[{$workflow_identifier}] = {$workflow_export};";
$code[] = "";
}
}
$code[] = ' return $workflows;';
$code = implode("\n", $code);
return array('workflow_default_workflows' => $code);
}
/**
* Implements hook_features_export_options().
*/
function workflow_features_export_options() {
$workflows = array();
foreach (workflow_get_workflows() as $workflow) {
$workflows[$workflow->name] = $workflow->name;
}
return $workflows;
}
/**
* Implements hook_features_revert().
*/
function workflow_features_revert($module) {
// Including the features inc to make sure this func is available during install of a Features module.
module_load_include('inc', 'features', 'features.export');
foreach (features_get_default('workflow', $module) as $key => $workflow) {
workflow_update_workflows_full_object($workflow);
}
}
/**
* Implements hook_features_export_rebuild().
*/
function workflow_features_export_rebuild($module) {
workflow_features_revert($module);
}
/**
* CRUD style functions below.
*/
/**
* For use by CRUD only, save everything from the CRUD formed object.
*
* @see workflow_get_workflows_full_object
*
* @param $workflow
* A fully loaded workflow object to save the states of.
*
* @return
* Returns whether the workflow was saved fully.
*/
function workflow_update_workflows_full_object($workflow) {
$workflow = (object) $workflow;
// Given a workflow in the format returned from export.
// First we grab the states, transitions and node_maps out.
$states = isset($workflow->states) ? $workflow->states : array();
$transitions = isset($workflow->transitions) ? $workflow->transitions : array();
$node_types = isset($workflow->node_types) ? $workflow->node_types : array();
unset($workflow->states, $workflow->transitions, $workflow->node_types);
// Then make a workflow so we can track by wid.
if ($orig_workflow = workflow_get_workflows_by_name($workflow->name)) {
$workflow->wid = $orig_workflow->wid;
}
workflow_update_workflows($workflow, FALSE);
// Cancel out if workflow failed to save.
if (!isset($workflow->wid) || empty($workflow->wid)) {
return FALSE;
}
// Workflow is now a fully vetted workflow object. We have NOT created a creation state with this.
// Then make states, marking state name to state sid.
$active_states = array();
foreach ($states as $state) {
$state = (object) $state;
$state->wid = $workflow->wid;
if ($orig_state = reset(workflow_get_workflow_states_by_wid_state($state->wid, $state->state))) {
$state->sid = $orig_state->sid;
}
workflow_update_workflow_states($state);
$active_states[$state->state] = $state->sid;
}
// Delete any states *not* in our original construction.
foreach (workflow_get_workflow_states_by_wid($workflow->wid) as $state) {
if (!in_array($state->sid, $active_states)) {
workflow_delete_workflow_states_by_sid($state->sid);
}
}
// Then make transitions with the state mapping.
$active_transitions = array();
foreach ($transitions as $transition) {
$transition = (object) $transition;
$transition->sid = $active_states[$transition->state];
$transition->target_sid = $active_states[$transition->target_state];
// Roles are exported by rolename, so need to translate to RID.
$transition->roles = !empty($transition->roles) ? _workflow_roles_to_rids($transition->roles) : '';
workflow_update_workflow_transitions($transition);
$active_transitions[] = $transition->tid;
}
// Delete any transitions in our workflow that are *not* in our original construction.
foreach (workflow_get_workflow_transitions_by_wid($workflow->wid) as $transition) {
if (!in_array($transition->tid, $active_transitions)) {
workflow_delete_workflow_transitions_by_tid($transition->tid);
}
}
// Then add the node_type mapping.
foreach ($node_types as $node_type) {
$node_type = (object) array(
'type' => $node_type,
'wid' => $workflow->wid
);
// Insert, nodes only have one workflow. Insert will delete any prior workflow assoc.
workflow_insert_workflow_type_map($node_type);
}
return TRUE;
}
/**
* For use by CRUD only, gather everything into the CRUD formed object.
*
* @param $name
* A string corresponding to a workflow object.
*
* @return
* A fully loaded workflow object with type and statue mappings.
*/
function workflow_get_workflows_full_object($name) {
if ($workflow = workflow_get_workflows_by_name($name)) {
// Now we need to add data to the object for each state, an array of sub-objects.
$options = array('status' => 1); // We only want active states for this export.
$active_states = array();
foreach (workflow_get_workflow_states_by_wid($workflow->wid, $options) as $index => $state) {
$active_states[$state->sid] = $state->state;
// Remove what we don't need to export.
unset($state->sid);
unset($state->wid);
$workflow->states[] = $state;
}
// Now we need to add data to the export for each transition, an array of sub-objects.
// Same goes for transitions, see above re: states.
foreach ($active_states as $sid => $state) {
// We're going to look everythign up by the start state, not state involved, to avoid dupes.
foreach (workflow_get_workflow_transitions_by_sid($sid, $options) as $transition) {
// And to get the target state (by name) we need to look it up too.
$target_state = workflow_get_workflow_states_by_sid($transition->target_sid);
$transition->state = $state;
$transition->target_state = $target_state->state;
unset($transition->sid, $transition->target_sid);
// Translate to role names so works cross install.
$transition->roles = !empty($transition->roles) ? _workflow_rids_to_roles($transition->roles) : '';
// Remove what we don't need to export.
unset($transition->tid);
$workflow->transitions[] = $transition;
}
}
// Now we need to add data to the export for each type map, an array of sub-objects.
// Same goes for node mappings, see above re: states.
foreach (workflow_get_workflow_type_map_by_wid($workflow->wid) as $index => $type_map) {
$workflow->node_types[] = $type_map->type;
}
}
return $workflow;
}
/**
* Internally cache the user roles as core doesn't.
*/
function _workflow_user_roles($reset = FALSE) {
$roles = &drupal_static(__FUNCTION__);
if ($reset || !isset($roles)) {
$roles = user_roles();
}
return $roles;
}
/**
* Translates a role string to RIDs for importing.
*
* @param $role_string
* A string of roles or fake 'author' role.
*
* @return
* A string of RIDs seperated by commas.
*/
function _workflow_roles_to_rids($role_string) {
$roles = _workflow_user_roles();
$rid_array = array();
foreach (explode(',', $role_string) as $role_name) {
if ($role_name === WORKFLOW_FEATURES_AUTHOR_NAME) {
$rid_array[] = 'author';
}
elseif ($role_name && in_array($role_name, $roles)) {
$rid_array[] = array_search($role_name, $roles);
}
}
return implode(',', $rid_array);
}
/**
* Translates a string of rids to role names for exporting.
*
* @param $rid_string
* A string of rids or fake 'author' role.
*
* @return
* A string of role names seperated by commas.
*/
function _workflow_rids_to_roles($rid_string) {
$roles = _workflow_user_roles();
$rid_array = explode(',', $rid_string);
// There may be a role named 'author', so make 'author' distinct.
$return = in_array('author', $rid_array) ? WORKFLOW_FEATURES_AUTHOR_NAME . ',' : '';
// Translate RIDs to rolenames.
$return .= implode(',', array_intersect_key($roles, array_flip($rid_array)));
return trim($return, ',');
}

View File

@@ -0,0 +1,12 @@
name = Workflow
description = "Allows the creation and assignment of arbitrary workflows to node types."
package = Workflow
core = 7.x
files[] = workflow.pages.inc
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,379 @@
<?php
/**
* @file
* Install, update and uninstall functions for the workflow module.
*
*/
/**
* Implements hook_install().
*/
function workflow_install() {
}
/**
* Implements hook_uninstall().
*/
function workflow_uninstall() {
variable_del('workflow_states_per_page');
// Delete type-workflow mapping variables.
foreach (node_type_get_types() as $type => $name) {
variable_del('workflow_' . $type);
}
}
/**
* Implements hook_schema().
*/
function workflow_schema() {
$schema['workflows'] = array(
'description' => 'Workflows',
'fields' => array(
'wid' => array(
'description' => 'The primary identifier for a node.',
'type' => 'serial',
'not null' => TRUE
),
'name' => array(
'description' => 'The name of the workflow (used as machine name for features intergration).',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => ''
),
'tab_roles' => array(
'description' => 'The role IDs that can access the workflow tabs on node pages.',
'type' => 'varchar',
'length' => '60',
'not null' => TRUE,
'default' => ''
),
'options' => array(
'description' => 'Additional settings for the workflow.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE
),
),
'primary key' => array('wid'),
'unique keys' => array('name' => array('name')),
);
$schema['workflow_type_map'] = array(
'fields' => array(
'type' => array(
'description' => 'The {node_type}.type the workflow is used on.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => ''
),
'wid' => array(
'description' => 'The {workflows}.wid this record affects.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10'
),
),
'indexes' => array(
'type' => array('type', 'wid'),
),
);
$schema['workflow_transitions'] = array(
'fields' => array(
'tid' => array(
'description' => 'The primary identifier for a workflow transition.',
'type' => 'serial',
'not null' => TRUE
),
'sid' => array(
'description' => 'The {workflow_states}.sid start state.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10'
),
'target_sid' => array(
'description' => 'The {workflow_states}.sid target state.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10'
),
'roles' => array(
'description' => 'The {role}.sid that a user must have to perform transition.',
'type' => 'varchar',
'length' => '255',
'not null' => FALSE,
),
),
'primary key' => array('tid'),
'indexes' => array(
'sid' => array('sid'),
'target_sid' => array('target_sid'),
),
);
$schema['workflow_states'] = array(
'fields' => array(
'sid' => array(
'description' => 'The primary identifier for a workflow state.',
'type' => 'serial',
'not null' => TRUE,
),
'wid' => array(
'description' => 'The {workflows}.wid this state is part of.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10'
),
'state' => array(
'description' => 'The name of the state.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
),
'weight' => array(
'description' => 'The weight (order) of the state.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'disp-width' => '4',
),
'sysid' => array(
'description' => 'The type of state, usually either WORKFLOW_CREATION or empty.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'disp-width' => '4',
),
'status' => array(
'description' => 'Whether the current state is active still.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
'disp-width' => '4',
),
),
'primary key' => array('sid'),
'indexes' => array(
'sysid' => array('sysid'),
'wid' => array('wid')
),
);
$schema['workflow_scheduled_transition'] = array(
'fields' => array(
'nid' => array(
'description' => 'The {node}.nid this transition is scheduled for.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'old_sid' => array(
'description' => 'The {workflow_states}.sid this state starts at.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'sid' => array(
'description' => 'The {workflow_states}.sid this state transitions to.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'uid' => array(
'description' => 'The user who scheduled this state transition.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'scheduled' => array(
'description' => 'The date this transition is scheduled for.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'comment' => array(
'description' => 'The comment explaining this transition.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
),
'indexes' => array(
'nid' => array('nid')
),
);
$schema['workflow_node_history'] = array(
'fields' => array(
'hid' => array(
'description' => 'The unique ID for this record.',
'type' => 'serial',
'not null' => TRUE
),
'nid' => array(
'description' => 'The {node}.nid this record is for.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'old_sid' => array(
'description' => 'The {workflow_states}.sid this transition started as.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE, '
default' => 0,
'disp-width' => '10',
),
'sid' => array(
'description' => 'The {workflow_states}.sid this transition transitioned to.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'uid' => array(
'description' => 'The {users}.uid who made this transition.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'stamp' => array(
'description' => 'The unique stamp for this transition.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'comment' => array(
'description' => 'The comment explaining this transition.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
),
'primary key' => array('hid'),
'indexes' => array(
'nid' => array('nid', 'sid'),
),
);
$schema['workflow_node'] = array(
'fields' => array(
'nid' => array(
'description' => 'The {node}.nid this record is for.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'sid' => array(
'description' => 'The {workflow_states}.sid that this node is currently in.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'uid' => array(
'description' => 'The {users}.uid who triggered this state.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'stamp' => array(
'description' => 'The unique stamp for the transition.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '11',
),
),
'primary key' => array('nid'),
'indexes' => array(
'nid' => array('nid', 'sid'),
),
);
return $schema;
}
/**
* Require highest 6.x release.
*/
function workflow_update_last_removed() {
return 6101;
}
/**
* Table update from 6 to 7. Adding a unique key for fields (already held unique in code).
*/
function workflow_update_7000() {
if (!db_index_exists('workflows', 'name')) {
db_add_unique_key('workflows', 'name', array('name'));
}
if (!db_index_exists('workflow_states', 'wid_state')) {
db_add_unique_key('workflow_states', 'wid_state', array('wid', 'state'));
}
}
/**
* Initialize all workflows to show watchdog messages.
*/
function workflow_update_7001() {
// Get all workflows.
$workflows = workflow_get_workflows();
foreach ($workflows as $workflow) {
// Add the option.
$workflow->options['watchdog_log'] = 1;
// Serialize the options array.
$workflow->options = serialize($workflow->options);
// Update the workflow without creating a creation state.
workflow_update_workflows($workflow, FALSE);
}
}
/**
* Add userid to scheduled transition table.
*/
function workflow_update_7002() {
db_add_field('workflow_scheduled_transition', 'uid', array(
'description' => 'The user who scheduled this state transition.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
'initial' => 0,
));
}

View File

@@ -0,0 +1,15 @@
(function ($) {
/**
* Custom summary for the module vertical tab.
*/
Drupal.behaviors.workflow_node_formFieldsetSummaries = {
attach: function (context) {
// Use the fieldset id to identify the vertical tab element
$('fieldset#edit-workflow', context).drupalSetSummary(function (context) {
return Drupal.checkPlain($('.form-item-workflow-scheduled input:checked', context).next('label').text());
});
}
};
})(jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,259 @@
<?php
/**
* @file
* Provide user interface for changing workflow state.
*/
/**
* Menu callback. Display workflow summary of a node.
*/
function workflow_tab_page($node = NULL) {
drupal_set_title($node->title);
$workflow = workflow_get_workflow_type_map_by_type($node->type);
$states = array();
$state_system_names = array();
foreach (workflow_get_workflow_states() as $data) {
$states[$data->sid] = check_plain(t($data->state));
$state_system_names[$data->sid] = check_plain($data->state);
}
$deleted_states = array();
$deleted_state_system_names = array();
$options = array('status' => 0);
foreach (workflow_get_workflow_states($options) as $data) {
$deleted_states[$data->sid] = check_plain(t($data->state));
$deleted_state_system_names[$data->sid] = check_plain($data->state);
}
$current = workflow_node_current_state($node);
// theme_workflow_history_current_state() must run state through check_plain().
$current_state = theme('workflow_history_current_state', array('state_name' => $states[$current], 'state_system_name' => $state_system_names[$current], 'sid' => $current));
$output = theme('workflow_current_state', array('state' => $states[$current], 'state_system_name' => $state_system_names[$current], 'sid' => $current));
// Show the form to allow state changing.
// But only if we are not at the terminal state.
$choices = workflow_field_choices($node);
if (count($choices) != 0 || count($choices) != 1 || $current != key($choices)) {
$form = drupal_get_form('workflow_tab_form', $node, $workflow->wid, $states, $current);
$output .= drupal_render($form);
}
$rows = array();
foreach (workflow_get_workflow_node_history_by_nid($node->nid) as $history) {
if ($history->sid == $current && !isset($deleted_states[$history->sid]) && !isset($current_themed)) {
// Theme the current state differently so it stands out.
$state_name = theme('workflow_history_current_state', array('state_name' => $states[$history->sid], 'state_system_name' => $state_system_names[$history->sid], 'sid' => $history->sid));
// Make a note that we have themed the current state; other times in the history
// of this node where the node was in this state do not need to be specially themed.
$current_themed = TRUE;
}
elseif (isset($deleted_states[$history->sid])) {
// The state has been deleted, but we include it in the history.
$state_name = theme('workflow_deleted_state', array('state_name' => $deleted_states[$history->sid], 'state_system_name' => $deleted_state_system_names[$history->sid], 'sid' => $history->sid));
$footer_needed = TRUE;
}
else {
// Regular state.
$state_name = check_plain(t($states[$history->sid]));
}
if (isset($deleted_states[$history->old_sid])) {
$old_state_name = theme('workflow_deleted_state', array('state_name' => $deleted_states[$history->old_sid], 'state_system_name' => $deleted_state_system_names[$history->old_sid], 'sid' => $history->old_sid));
$footer_needed = TRUE;
}
elseif (isset($states[$history->old_sid])) {
$old_state_name = check_plain(t($states[$history->old_sid]));
}
else {
$old_state_name = '*';
}
$variables = array(
'history' => $history,
'old_sid' => $history->old_sid,
'old_state_name' => $old_state_name,
'sid' => $history->sid,
'uid' => $history->uid,
'state_name' => $state_name,
);
// Allow other modules to modify the row.
drupal_alter('workflow_history', $variables);
$rows[] = theme('workflow_history_table_row', $variables);
}
// Mark the first and last rows.
$rows[0]['class'][] = 'first';
$last = count($rows) - 1;
$rows[$last]['class'][] = 'last';
// Only display the table if there's anything in it.
if ($rows) {
$output .= theme('workflow_history_table', array('rows' => $rows, 'footer' => !empty($footer_needed)));
$output .= theme('pager', array('tags' => variable_get('workflow_states_per_page', 20)));
}
return $output;
}
/*
* Theme one workflow history table row.
*
* $old_state_name and $state_name must be run through check_plain(t()) prior
* to calling this theme function.
*/
function theme_workflow_history_table_row($variables) {
$row = array();
$old_state_name = $variables['old_state_name'];
$state_name = $variables['state_name'];
$history = $variables['history'];
$account = user_load($variables['uid']);
$row = array(
'data' => array(
array('data' => format_date($history->stamp), 'class' => array('timestamp')),
array('data' => $old_state_name, 'class' => array('previous-state-name')),
array('data' => $state_name, 'class' => array('state-name')),
array('data' => theme('username', array('account' => $account)), 'class' => array('user-name')),
array('data' => filter_xss($history->comment), 'class' => array('log-comment')),
),
'class' => array('workflow_history_row'),
);
if (!empty($variables['extra'])) {
$row['data'][] = $variables['extra'];
}
return $row;
}
/*
* Theme entire workflow history table.
*/
function theme_workflow_history_table($variables) {
$rows = $variables['rows'];
$footer = $variables['footer'];
$headers = array(t('Date'), t('Old State'), t('New State'), t('By'), t('Comment'));
$output = theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Workflow History')));
if ($footer) {
$output .= t('*State is no longer available.');
}
return $output;
}
/**
* Theme the current state in the workflow history table.
*/
function theme_workflow_history_current_state($variables) {
return check_plain(t($variables['state_name']));
}
/**
* Theme a deleted state in the workflow history table.
*/
function theme_workflow_deleted_state($variables) {
return check_plain(t($variables['state_name'])) . '*';
}
/**
* Form builder. Allow workflow state change and scheduling from workflow tab.
*
* @param $node
* Node for which workflow information will be displayed.
* @param $wid
* ID of workflow to display.
* @param $states
* Array of states for the workflow.
* @param $current
* Current workflow state of this node.
* @return
* Form definition array.
*/
function workflow_tab_form($form, $form_state, $node, $wid, $states, $current) {
// Tell FAPI where this form is.
form_load_include($form_state, 'inc', 'workflow', 'workflow.pages');
// Let's make sure we should be here.
if (workflow_node_tab_access($node) === FALSE) {
return;
}
$form['#tab'] = TRUE;
$choices = workflow_field_choices($node);
$min = ($states[$current] == t('(creation)') ? 1 : 2);
// Only build form if user has possible target state(s).
if (count($choices) >= $min) {
$workflow = workflow_get_workflow_type_map_by_type($node->type);
$workflow = workflow_get_workflows_by_wid($workflow->wid);
$form['#wf'] = $workflow;
$form['#choices'] = $choices;
$name = t($workflow->name);
$timestamp = '';
$comment = '';
// See if scheduling information is present.
if (!empty($node->workflow_scheduled_timestamp) && !empty($node->workflow_scheduled_sid)) {
global $user;
if (variable_get('configurable_timezones', 1) && $user->uid && drupal_strlen($user->timezone)) {
$timezone = $user->timezone;
}
else {
$timezone = variable_get('date_default_timezone', 0);
}
// The default value should be the upcoming sid.
$current = $node->workflow_scheduled_sid;
$timestamp = $node->workflow_scheduled_timestamp;
$comment = $node->workflow_scheduled_comment;
}
// Include the same form elements here that are included on a
// regular node editing page. $form is modified by reference.
workflow_node_form($form, $form_state, t('Change !name state', array('!name' => $name)), $name, $current, $choices, $timestamp, $comment);
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Update workflow'),
);
}
return $form;
}
/**
* Submit handler for the form on the workflow tab.
*
* @see workflow_tab_form()
*/
function workflow_tab_form_submit($form, &$form_state) {
// The entire node object was stashed in the form.
$node = $form_state['values']['node'];
if (isset($form_state['values']['workflow'])) {
$node->workflow = $form_state['values']['workflow'];
$node->workflow_comment = isset($form_state['values']['workflow_comment']) ?
$form_state['values']['workflow_comment'] : '';
if (!empty($form_state['values']['workflow_scheduled'])) {
$node->workflow_scheduled = $form_state['values']['workflow_scheduled'];
}
if (!empty($form_state['values']['workflow_scheduled_date'])) {
$node->workflow_scheduled_date = $form_state['values']['workflow_scheduled_date'];
}
if (!empty($form_state['values']['workflow_scheduled_hour'])) {
$node->workflow_scheduled_hour = $form_state['values']['workflow_scheduled_hour'];
}
if (!empty($form_state['values']['workflow_scheduled_timezone'])) {
$node->workflow_scheduled_timezone = $form_state['values']['workflow_scheduled_timezone'];
}
}
// ALERT: Rules that use node_save to check the node transition are going to be missed if
// the tab form is used to check for the change. It is *always* better practice to use
// the transition change itself as your value to check for changes with Rules and other
// behaviors. Do NOT rely on node_save() to drive transition changes.
workflow_transition($node, $node->workflow);
}

View File

@@ -0,0 +1,229 @@
<?php
/**
* @file
* Tokens hooks for Workflow module.
*/
/**
* Implements hook_tokens().
*/
function workflow_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']);
$langcode = !empty($options['language']->language) ? $options['language']->language : LANGUAGE_NONE;
$date = REQUEST_TIME;
if (($type == 'node' || $type == 'workflow') && !empty($data['node']) && !empty($data['node']->nid)) {
$node = $data['node'];
// Get a list of all possible states for returning state names.
$states = workflow_get_workflow_states_all();
if ($workflow = workflow_get_workflow_type_map_by_type($node->type)) {
// @TODO: If we ever move Workflow to allow M:M content types to workflows, this will need updating.
if ($workflow = workflow_get_workflows_by_wid($workflow->wid)) {
$wid = $workflow->wid;
$last_history = workflow_get_recent_node_history($node->nid);
if (isset($node->workflow) && !isset($node->workflow_stamp)) {
// The node is being submitted but the form data has not been saved to the database yet,
// so we set the token values from the workflow form fields.
$sid = $node->workflow;
$old_sid = isset($last_history->old_sid) ? $last_history->old_sid : workflow_get_creation_state_by_wid($workflow->wid);
$user_name = $node->uid ? (isset($node->name) ? $node->name
: variable_get('anonymous', 'Anonymous'))
: variable_get('anonymous', 'Anonymous');
$uid = $node->uid;
$account = user_load($node->uid);
$mail = ($node->uid && isset($node->user_mail)) ? $node->user_mail : '';
$comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
}
else {
if (empty($last_history)) {
// If the node has no workflow history,
// the node is being inserted and will soon be transitioned to the first valid state.
// We find this state using the same logic as workflow_nodeapi().
if ($choices = workflow_field_choices($node)) {
// @TODO: Some users, like anonymous, may not be allowed
// to cause transitions, so there will be no choices.
$keys = array_keys($choices);
$sid = array_shift($keys);
$old_sid = workflow_get_creation_state_by_wid($workflow->wid);
}
else {
// What to choose?
$sid = $node->workflow;
$old_sid = $workflow->creation_state;
}
$sid = workflow_get_workflow_states_by_sid($sid)->sid;
$old_sid = workflow_get_workflow_states_by_sid($old_sid)->sid;
$user_name = $node->uid ? $node->name : variable_get('anonymous', 'Anonymous');
$uid = $node->uid;
$account = user_load($node->uid);
$mail = ($node->uid && isset($node->user_mail)) ? $node->user_mail : '';
$comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
}
else {
// Sometimes there is no workflow set (edit?).
if (!isset($node->workflow)) {
$node->workflow = $last_history->sid;
}
// Is a transition in progress?
if ($node->workflow != $last_history->sid) {
$sid = $node->workflow;
$old_sid = $last_history->sid;
$date = $node->workflow_stamp;
$uid = $node->uid;
$account = user_load($node->uid);
}
else {
// Not a transition.
$sid = $last_history->sid;
$old_sid = $last_history->old_sid;
$date = $last_history->stamp;
$uid = $last_history->uid;
$account = user_load($last_history->uid);
}
// Default to the most recent transition data in the workflow history table.
$user_name = $account->uid ? $account->name : variable_get('anonymous', 'Anonymous');
$mail = $account->uid ? $account->mail : '';
$comment = $last_history->comment;
}
}
foreach ($tokens as $name => $original) {
switch ($name) {
case 'workflow-name':
$replacements[$original] = $sanitize ? check_plain($workflow->name) : $workflow->name;
break;
case 'workflow-current-state-name':
$replacements[$original] = $sanitize ? check_plain($states[$sid]) : $states[$sid];
break;
case 'workflow-old-state-name':
$replacements[$original] = $sanitize ? check_plain($states[$old_sid]) : $states[$old_sid];
break;
case 'workflow-current-state-updating-user-name':
$name = format_username($account);
$replacements[$original] = $sanitize ? check_plain($name) : $name;
break;
case 'workflow-current-state-updating-user-link':
$replacements[$original] = theme('username', array('account' => $account));
break;
case 'workflow-current-state-updating-user-uid':
// User IDs are integers only and do not need sanitization.
$replacements[$original] = $uid;
break;
case 'workflow-current-state-updating-user-mail':
$replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail;
break;
case 'workflow-current-state-updating-user-mailto':
$replacements[$original] = '<a href="mailto:' . check_plain($account->mail) . '">' . check_plain($user_name) . '</a>';
break;
case 'workflow-current-state-log-entry':
$replacements[$original] = $sanitize ? check_markup($comment, filter_default_format(), $langcode) : $comment;
break;
case 'workflow-current-state-date-iso':
$replacements[$original] = format_date($date, 'custom', 'c', NULL, $langcode);
break;
case 'workflow-current-state-date-tstamp':
$replacements[$original] = $sanitize ? check_plain($date) : $date;
break;
case 'workflow-current-state-date-formatted':
$replacements[$original] = format_date($date, 'medium', '', NULL, $langcode);
break;
default:
// Add support for custom date formats. (see token.tokens.inc)
// @todo Remove when http://drupal.org/node/1173706 is fixed.
$date_format_types = system_get_date_types();
foreach ($date_format_types as $date_type => $date_info) {
if ($name == 'workflow-current-state-date-' . $date_type) {
$replacements[$original] = format_date($date, $date_type, '', NULL, $langcode);
}
}
break;
}
}
}
}
}
return $replacements;
}
/**
* Implements hook_token_info().
*/
function workflow_token_info() {
$type = array(
'name' => t('Workflow'),
'description' => t('Tokens related to workflows.'),
'needs-data' => 'node',
);
$tokens = array();
$tokens['workflow-name'] = array(
'name' => t('Workflow name'),
'description' => t('Name of workflow appied to this node'),
);
$tokens['workflow-current-state-name'] = array(
'name' => t('Current state name'),
'description' => t('Current state of content'),
);
$tokens['workflow-old-state-name'] = array(
'name' => t('Old state name'),
'description' => t('Old state of content'),
);
$tokens['workflow-current-state-updating-user-name'] = array(
'name' => t('Username of last state changer'),
'description' => t('Username of last state changer'),
);
$tokens['workflow-current-state-updating-user-link'] = array(
'name' => t('Username link of last state changer'),
'description' => t('Themed Username of last state changer'),
);
$tokens['workflow-current-state-updating-user-uid'] = array(
'name' => t('Uid of last state changer'),
'description' => t('uid of last state changer'),
);
$tokens['workflow-current-state-updating-user-mail'] = array(
'name' => t('Email of last state changer'),
'description' => t('email of last state changer'),
);
$tokens['workflow-current-state-updating-user-mail'] = array(
'name' => t('Email link of last state changer'),
'description' => t('email link of last state changer'),
);
$tokens['workflow-current-state-log-entry'] =array(
'name' => t('Last workflow comment log'),
'description' => t('Last workflow comment log'),
);
$tokens['workflow-current-state-date-iso'] = array(
'name' => t('Current state date (ISO)'),
'description' => t('Date of last state change (ISO)'),
);
$tokens['workflow-current-state-date-tstamp'] = array(
'name' => t('Current state date (timestamp)'),
'description' => t('Date of last state change (timestamp)'),
);
$tokens['workflow-current-state-date-formatted'] = array(
'name' => t('Current state date (formatted by site default)'),
'description' => t('Date of last state change (formatted by site default)'),
);
// Add support for custom date formats. (see token.tokens.inc)
// @todo Remove when http://drupal.org/node/1173706 is fixed.
$date_format_types = system_get_date_types();
foreach ($date_format_types as $date_type => $date_info) {
$tokens['workflow-current-state-date-' . $date_type] = array(
'name' => t('Current state date (using @format format)', array('@format' => $date_info['title'])),
'description' => t("A date in '@type' format. (%date)", array('@type' => $date_type, '%date' => format_date(REQUEST_TIME, $date_type))),
'module' => 'workflow',
);
}
return array(
'types' => array('workflow' => $type),
'tokens' => array('workflow' => $tokens, 'node' => $tokens),
);
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* Workflow_access records are a **faux-exportable** component.
*/
/**
* Helper to find the roles needs by an export.
*
* @return array
* A workflow_name-indexed hash of non-default roles in workflow_access for
* that workflow.
*/
function _workflow_access_get_affected_roles() {
$sql = <<<SQL
SELECT DISTINCT
w.name AS wname,
r.name AS rname
FROM {workflow_access} wa
INNER JOIN {workflow_states} ws ON wa.sid = ws.sid
INNER JOIN {workflows} w ON ws.wid = w.wid
INNER JOIN {role} r ON wa.rid = r.rid
SQL;
$roles = array();
foreach (db_query($sql) as $row) {
$roles[$row->wname][$row->rname] = $row->rname;
}
return $roles;
}
/**
* Implements hook_features_export_options().
*
* Provide a list of all workflows as selectable Feature components.
*
* We'll make all workflow access records related to the selected workflows
* available for export.
*/
function workflow_access_features_export_options() {
$workflows = array();
foreach (workflow_get_workflows() as $workflow) {
$workflows[$workflow->name] = t('workflow access configuration for: ') . $workflow->name;
}
return $workflows;
}
/**
* Implements hook_features_export().
*
* Inform Features about dependencies.
*/
function workflow_access_features_export($data, &$export, $module_name = '') {
$roles = _workflow_access_get_affected_roles();
$export['dependencies']['workflow_access'] = 'workflow_access';
$export['dependencies']['workflow'] = 'workflow';
$pipe = array();
foreach ($data as $component) {
$export['features']['workflow_access'][$component] = $component;
// Access configuration for a workflow needs that worfklow to exist.
$pipe['workflow'][$component] = $component;
// And it needs the roles to which rights are granted.
foreach ($roles[$component] as $rname) {
$pipe['user_role'][$rname] = $rname;
}
}
return $pipe;
}
/**
* Implements hook_features_export_render().
*
* Instruct Features which php code to generate, including the code-ified
* workflow access records we want to export from db into code.
*/
function workflow_access_features_export_render($module_name, $data, $export = NULL) {
$code = array();
$code[] = ' $workflows = array();';
// For each selected workflow, fetch all related workflow access records
// we want to put into code.
foreach ($data as $workflow_name) {
$workflow = workflow_get_workflows_by_name($workflow_name);
if (!empty($workflow)) {
$states = workflow_get_workflow_states_by_wid($workflow->wid);
if (!empty($states)) {
$code[] = "\n \$workflows['$workflow_name'] = array();";
foreach ($states as $state) {
$access_records = workflow_access_get_features_workflow_access_by_sid($state->sid);
if (!empty($access_records)) {
$state_name = $state->state;
$code[] = " \$workflows['$workflow_name']['$state_name'] = array();";
foreach ($access_records as $record) {
$rname = $record->rname;
unset($record->rname);
$code[] = " \$workflows['$workflow_name']['$state_name']['$rname'] = " . features_var_export($record, ' ') . ';';
}
}
}
}
}
}
$code[] = "\n return \$workflows;";
$code = implode("\n", $code);
return array('workflow_access_features_default_settings' => $code);
}
/**
* Implements hook_features_rebuild().
*
* Instruct Features to insert our records (that were exported into code)
* into the workflow_access table.
*/
function workflow_access_features_rebuild($module) {
$workflow_access_records = module_invoke($module, 'workflow_access_features_default_settings');
// retrieve the workflow ids
$wids = array();
foreach ($workflow_access_records as $workflow_name => $state) {
$workflow = workflow_get_workflows_by_name($workflow_name);
$wids[$workflow_name] = $workflow->wid;
}
foreach ($wids as $workflow_name => $wid) {
$states = workflow_get_workflow_states_by_wid($wid);
foreach ($states as $state) {
// Remove all workflow access records for states belonging to this
// workflow. We don't want lingering entries - we only want the ones we're
// about to insert.
workflow_access_delete_workflow_access_by_sid($state->sid);
}
}
// Insert our workflow access records. They look like
// workflow_name[state_name][role_name] => array(grant_name => 0|1, ...)
foreach ($workflow_access_records as $workflow_name => $states) {
foreach ($states as $state_name => $records) {
$state = workflow_get_workflow_states_by_wid_state($wids[$workflow_name], $state_name);
if (is_array($state)) {
$state = array_pop($state);
}
foreach ($records as $rname => $record) {
$record['sid'] = (is_array($state)) ? $state[0]->sid : $state->sid;
if ($rname == WORKFLOW_FEATURES_AUTHOR_NAME) {
$record['rid'] = -1;
}
else {
$role = user_role_load_by_name($rname);
$record['rid'] = $role->rid;
}
workflow_access_insert_workflow_access_by_sid($record);
}
}
}
}
/**
* Implements hook_features_revert().
*/
function workflow_access_features_revert($module) {
workflow_access_features_rebuild($module);
}
/**
* Get workflow_access object like below by state id.
*
* array(
* 'rname' => 'authenticated user',
* 'grant_view' => 1,
* 'grant_update' => 0,
* 'grant_delete' => 0,
* );
*
* State id and workflow id are not returned because they are implicit for a
* given sid.
*/
function workflow_access_get_features_workflow_access_by_sid($sid) {
// Get all workflow access rules for a sid, where wa.rid is either a valid role or -1,
// stands for the author
$sql = <<<SQL
SELECT
r.name as rname,
wa.grant_view, wa.grant_update, wa.grant_delete
FROM {workflow_access} wa
LEFT JOIN {role} r ON wa.rid = r.rid
WHERE
wa.sid = :sid AND (wa.rid = r.rid OR wa.rid = -1)
SQL;
$results = db_query($sql, array(':sid' => $sid));
$records = $results->fetchAll();
foreach ($records as $record) {
if (empty($record->rname)) {
$record->rname = WORKFLOW_FEATURES_AUTHOR_NAME;
}
}
return $records;
}

View File

@@ -0,0 +1,11 @@
name = Workflow access
description = Content access control based on workflows and roles.
dependencies[] = workflow
package = Workflow
core = 7.x
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,62 @@
<?php
/**
* @file
* Workflow access installation.
*/
/**
* Implements hook_install().
*/
function workflow_access_install() { }
/**
* Implements hook_uninstall().
*/
function workflow_access_uninstall() { }
/**
* Implements hook_schema().
*/
function workflow_access_schema() {
$schema['workflow_access'] = array(
'description' => 'Workflow access tables',
'fields' => array(
'sid' => array(
'type' => 'serial',
'not null' => TRUE,
),
'rid' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'grant_view' => array(
'type' => 'int',
'unsigned' => TRUE,
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
'grant_update' => array(
'type' => 'int',
'unsigned' => TRUE,
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
'grant_delete' => array(
'type' => 'int',
'unsigned' => TRUE,
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'rid' => array('rid'),
'sid' => array('sid'),
),
);
return $schema;
}

View File

@@ -0,0 +1,358 @@
<?php
/**
* @file
* Provides node access permissions based on workflow states.
*/
/**
* Implements hook_menu().
*/
function workflow_access_menu() {
$items = array();
$items["admin/config/workflow/access/%workflow"] = array(
'title' => 'Access',
'access arguments' => array('administer workflow'),
'page callback' => 'drupal_get_form',
'page arguments' => array('workflow_access_form', 4),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* 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($account, $op) {
return array(
'workflow_access' => array_keys($account->roles),
'workflow_access_owner' => array($account->uid),
);
}
/**
* Implements hook_node_access_records().
*
* Returns a list of grant records for the passed in node object.
*/
function workflow_access_node_access_records($node) {
$grants = array();
if ($state = workflow_get_workflow_node_by_nid($node->nid)) {
$state = workflow_get_workflow_states_by_sid($state->sid);
foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $grant) {
$grants[] = array(
'realm' => ($grant->rid == -1) ? 'workflow_access_owner' : 'workflow_access',
'gid' => ($grant->rid == -1) ? $node->uid : $grant->rid,
'grant_view' => $grant->grant_view,
'grant_update' => $grant->grant_update,
'grant_delete' => $grant->grant_delete,
'priority' => variable_get('workflow_access_priority', 0),
);
}
}
return $grants;
}
/**
* Implements hook_node_access_explain().
* This is a Devel Node Access hook.
*/
function workflow_access_node_access_explain($row) {
static $interpretations = array();
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', array('%role' => $roles[$row->gid]));
break;
}
return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
}
/**
* Implements hook_workflow_operations().
* Create action link for access form.
*/
function workflow_access_workflow_operations($op, $workflow = NULL, $state = NULL) {
switch ($op) {
case 'workflow':
$alt = t('Control content access for @wf', array('@wf' => $workflow->name));
$actions = array(
'workflow_access_form' => array(
'title' => t('Access'),
'href' => "admin/config/workflow/access/$workflow->wid",
'attributes' => array('alt' => $alt, 'title' => $alt),
),
);
return $actions;
}
}
/**
* Implements hook_form().
*
* Add a "three dimensional" (state, role, permission type) configuration
* interface to the workflow edit form.
*/
function workflow_access_form($form, $form_state, $workflow) {
if ($workflow) {
drupal_set_title(t('@name Access', array('@name' => $workflow->name)));
}
else {
drupal_set_message(t('Unknown workflow'));
drupal_goto('admin/config/workflow/workflow');
}
$bc = array(l(t('Home'), '<front>'));
$bc[] = l(t('Configuration'), 'admin/config');
$bc[] = l(t('Workflow'), 'admin/config/workflow');
$bc[] = l(t('Workflow'), 'admin/config/workflow/workflow');
$bc[] = l(t($workflow->name), "admin/config/workflow/workflow/$workflow->wid");
// $bc[] = l(t('Access'), "admin/config/workflow/access/$workflow->wid");
drupal_set_breadcrumb($bc);
$form = array('#tree' => TRUE);
$form['#wid'] = $workflow->wid;
// A list of roles available on the site and our
// special -1 role used to represent the node author.
$rids = user_roles(FALSE, 'participate in workflow');
$rids['-1'] = t('author');
// Add a table for every workflow state.
$options = array('status' => 1);
foreach (workflow_get_workflow_states_by_wid($workflow->wid, $options) as $state) {
if ($state->state == t('(creation)')) {
// No need to set perms on creation.
continue;
}
$view = $update = $delete = array();
$count = 0;
foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $access) {
$count++;
if ($access->grant_view) {
$view[] = $access->rid;
}
if ($access->grant_update) {
$update[] = $access->rid;
}
if ($access->grant_delete) {
$delete[] = $access->rid;
}
}
// Allow view grants by default for anonymous and authenticated users,
// if no grants were set up earlier.
if (!$count) {
$view = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID);
}
// TODO better tables using a #theme function instead of direct #prefixing
$form[$state->sid] = array(
'#type' => 'fieldset',
'#title' => t('@state', array('@state' => $state->state)),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#tree' => TRUE,
);
$form[$state->sid]['view'] = array(
'#type' => 'checkboxes',
'#options' => $rids,
'#default_value' => $view,
'#title' => t('Roles who can view posts in this state'),
'#prefix' => '<table width="100%" style="border: 0;"><tbody style="border: 0;"><tr><td>',
);
$form[$state->sid]['update'] = array(
'#type' => 'checkboxes',
'#options' => $rids,
'#default_value' => $update,
'#title' => t('Roles who can edit posts in this state'),
'#prefix' => "</td><td>",
);
$form[$state->sid]['delete'] = array(
'#type' => 'checkboxes',
'#options' => $rids,
'#default_value' => $delete,
'#title' => t('Roles who can delete posts in this state'),
'#prefix' => "</td><td>",
'#suffix' => "</td></tr></tbody></table>",
);
}
$form['warning'] = array(
'#type' => 'markup',
'#markup' => '<p><strong>'
. t('WARNING:')
. '</strong> '
. t('Use of the "Edit any," "Edit own," and even "View published content" permissions
for the content type may override these access settings.')
. '</p>',
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
return $form;
// Place our block comfortably down the page.
$form['submit']['#weight'] = 10;
$form['#submit'][] = 'workflow_access_form_submit';
}
/**
* Store permission settings for workflow states.
*/
function workflow_access_form_submit($form, &$form_state) {
foreach ($form_state['values'] as $sid => $access) {
// Ignore irrelevant keys.
if (!is_numeric($sid)) {
continue;
}
$grants = array();
foreach ($access['view'] as $rid => $checked) {
$data = array(
'sid' => $sid,
'rid' => $rid,
'grant_view' => (!empty($checked)) ? (bool) $checked : 0,
'grant_update' => (!empty($access['update'][$rid])) ? (bool) $access['update'][$rid] : 0,
'grant_delete' => (!empty($access['delete'][$rid])) ? (bool) $access['delete'][$rid] : 0,
);
$id = workflow_access_insert_workflow_access_by_sid($data);
}
// Update all nodes having same workflow state to reflect new settings.
foreach (workflow_get_workflow_node_by_sid($sid) as $data) {
// Instead of trying to construct what the grants should be per node as we save.
// Let's fall back on existing node grant systems that will find it for us.
$node = node_load($data->nid);
node_access_acquire_grants($node);
}
}
drupal_set_message(t('Workflow access permissions updated.'));
$form_state['redirect'] = 'admin/config/workflow/workflow/' . $form['#wid'];
}
/**
* Implements hook_workflow().
*
* Update grants when a node changes workflow state.
* This is already called when node_save is called.
*/
function workflow_access_workflow($op, $old_sid, $sid, $node) {
// ALERT:
// This is a tricky spot when called on node_insert as part of the transition from create to state1.
// node_save invokes this function as a hook before calling node_access_acquire_grants.
// But when it calls node_access_acquire_grants later, it does so without deleting the access
// set when calling workflow_node_insert because it is an insert and no prior grants are expected.
// This leads to a SQL error of duplicate grants which causes a rollback of all changes.
// Unfortunately, setting access rights isn't the only thing we're doing on node_insert so we
// can't skip the whole thing. So we need to fix it further downstream in order to get this to work.
// Here we don't want to run this in the case of (and ONLY in the case of) a brand new node.
// Node access grants will be run as part of node_save's own granting after this.
//
// NOTE: Any module that sets node access rights on insert will hit this situation.
//
if ($old_state = workflow_get_workflow_states_by_sid($old_sid)) {
if ($op == 'transition post' && $old_sid != $sid && (empty($node->is_new) || !$node->is_new)) {
node_access_acquire_grants($node);
}
}
}
/**
* 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) {
$results = db_query('SELECT * from {workflow_access} where sid = :sid', array(':sid' => $sid));
return $results->fetchAll();
}
/**
* Given a sid and a rid (the unique key), delete all access data for this state.
*/
function workflow_access_delete_workflow_access_by_sid_rid($sid, $rid) {
db_delete('workflow_access')->condition('sid', $sid)->condition('rid', $rid)->execute();
}
/**
* Given a sid, delete all access data for this state.
*/
function workflow_access_delete_workflow_access_by_sid($sid) {
db_delete('workflow_access')->condition('sid', $sid)->execute();
}
/**
* Given data, insert into workflow access - we never update.
*/
function workflow_access_insert_workflow_access_by_sid(&$data) {
$data = (object) $data;
workflow_access_delete_workflow_access_by_sid_rid($data->sid, $data->rid);
drupal_write_record('workflow_access', $data);
}
/**
* Implements hook_form_alter().
*
* Tell the Features module that we intend to provide one exportable component.
*/
function workflow_access_form_alter(&$form, &$form_state, $form_id) {
switch ($form_id) {
case 'workflow_admin_ui_types_form':
$form['workflow_access'] = array(
'#type' => 'fieldset',
'#title' => t('Workflow Access'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['workflow_access']['workflow_access_priority'] = array(
'#type' => 'weight',
'#delta' => 10,
'#title' => t('Workflow Access Priority'),
'#default_value' => variable_get('workflow_access_priority', 0),
'#description' => t('This sets the node access priority. This can be dangerous. If there is any doubt,
leave it at 0.')
. ' '
. l(t('Read the manual.'), 'https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_access_records/7'),
);
$form['#submit'][] = 'workflow_access_priority_submit';
return;
}
}
/**
* Submit handler.
*/
function workflow_access_priority_submit($form, &$form_state) {
variable_set('workflow_access_priority', $form_state['values']['workflow_access']['workflow_access_priority']);
}
/**
* Implements hook_features_api().
*
* Tell the Features module that we intend to provide one exportable component.
*/
function workflow_access_features_api() {
return array(
'workflow_access' => array(
'name' => t('Workflow access'),
'file' => drupal_get_path('module', 'workflow_access') . '/workflow_access.features.inc',
'default_hook' => 'workflow_access_features_default_settings',
'default_file' => FEATURES_DEFAULTS_INCLUDED,
'feature_source' => TRUE,
),
);
}

View File

@@ -0,0 +1,37 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
INTRODUCTION
------------
There is an important issue to keep in mind as you use action hooks and workflow!!
If the machine readable name of the content type on which you want to define actions
in the workflow exceeds 20 characters then the actions you define will not be visible
in the screen where you define the triggers nor will they execute.
The reason is that the length of the field "op" in the "trigger-assignments" table
is 32 characters. The name of this "op"-field is a concatenation of the string
"workflow-" with the machine readable name of the content type, another "-" and the
transition-id on which the action has to be performed. If the latter has a length of
1 then this leaves 32 - 9 - 1 - 1 = 21 characters for the machine readable name of
the content type.
So: KEEP YOUR CONTENT TYPE NAMES SHORT.
Unfortunately the code that handles this is in core, so not readily changable. If
you have trouble seeing your actions check your name lengths.
See further discussion at:
http://drupal.org/node/585726
See request put to core to make the change at:
http://drupal.org/node/1062068
Closed and told to have workflow make the table change ourselves. Given that changing
name lengths haphazardly would spread the bugs around even more this approach was
not followed.

View File

@@ -0,0 +1,12 @@
name = Workflow actions and triggers
description = Provides actions and triggers for workflows.
dependencies[] = workflow
dependencies[] = trigger
package = Workflow
core = 7.x
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,245 @@
<?php
/**
* @file
* Provide actions and triggers for workflows.
* Why it's own module? Some sites prefer rules, some prefer actions,
* all prefer a lower code footprint and better performance.
* Additional creadit to gcassie ( http://drupal.org/user/80260 ) for
* the initial push to split actions out of core workflow.
*/
/**
* Implements hook_hook_info().
* Expose each transition as a hook.
*/
function workflow_actions_trigger_info() {
$states = array();
foreach (workflow_get_workflow_states() as $data) {
$states[$data->sid] = check_plain($data->state);
}
if (empty($states)) {
return array();
}
$trigger_page = drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow';
// TODO these should be pulled into their own DB function calls.
if ($trigger_page && $wid = arg(4)) {
$result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' .
'FROM {workflow_type_map} tm ' .
'LEFT JOIN {workflows} w ON tm.wid = w.wid ' .
'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' .
'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' .
'WHERE w.wid = :wid AND ws.status = 1 AND wt.target_sid IS NOT NULL ' .
'ORDER BY tm.type, ws.weight', array(':wid' => $wid));
}
else {
$result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' .
'FROM {workflow_type_map} tm ' .
'LEFT JOIN {workflows} w ON tm.wid = w.wid ' .
'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' .
'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' .
'WHERE ws.status = 1 AND wt.target_sid IS NOT NULL ' .
'ORDER BY tm.type, ws.weight');
}
$creation_state = t('(creation)');
foreach ($result as $data) {
$creation_flag = FALSE;
if ($states[$data->sid] == $creation_state) {
$creation_flag = TRUE;
}
$pseudohooks['workflow-' . $data->type . '-' . $data->tid] =
array('label' => t('When %type moves from %state to %target_state',
array('%type' => $data->type, '%state' => $states[$data->sid], '%target_state' => $states[$data->target_sid])),
'workflow_creation_state' => $creation_flag,
);
}
// $pseudohooks will not be set if no workflows have been assigned
// to node types.
if (isset($pseudohooks)) {
return array(
'workflow' => $pseudohooks,
);
}
if ($trigger_page) {
drupal_set_message(t('Either no transitions have been set up or this workflow has not yet been ' .
'assigned to a content type. To enable the assignment of actions, edit the workflow to assign ' .
'permissions for roles to do transitions. After that is completed, transitions will appear here ' .
'and you will be able to assign actions to them.'));
}
}
/**
* Implements hook_workflow().
*
* @param $hook
* The current workflow operation: 'transition pre' or 'transition post'.
* @param $old_state
* The state ID of the current state.
* @param $new_state
* The state ID of the new state.
* @param $node
* The node whose workflow state is changing.
*/
function workflow_actions_workflow($op, $old_state, $new_state, $node) {
switch ($op) {
case 'transition post':
// A transition has occurred; fire off actions associated with this transition.
if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
$hook = 'workflow-' . $node->type . '-' . $transition->tid;
$aids = trigger_get_assigned_actions($hook);
if ($aids) {
$context = array(
'hook' => $hook,
);
// We need to get the expected object if the action's type is not 'node'.
// We keep the object in $objects so we can reuse it if we have multiple actions
// that make changes to an object.
foreach ($aids as $aid => $action_info) {
if ($action_info['type'] != 'node') {
if (!isset($objects[$action_info['type']])) {
$objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
}
// Since we know about the node, we pass that info along to the action.
$context['node'] = $node;
$result = actions_do($aid, $objects[$action_info['type']], $context);
}
else {
actions_do($aid, $node, $context);
}
}
}
}
break;
case 'transition delete':
if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
$actions = workflow_actions_get_actions_by_tid($transition->tid);
foreach ($actions as $aid) {
workflow_actions_remove($transition->tid, $aid);
}
}
break;
}
}
/**
* Implements hook_workflow_operations().
* Called in workflow.admin.inc to add actions for states.
*/
function workflow_actions_workflow_operations($level, $workflow = NULL, $state = NULL) {
if ($workflow) {
return array('workflow_overview_actions' => array(
'title' => t('Actions'),
'href' => 'admin/structure/trigger/workflow/' . $workflow->wid),
);
}
}
/**
* Remove an action assignment programmatically.
*
* Helpful when deleting a workflow.
*
* @param $tid
* Transition ID.
* @param $aid
* Action ID.
*/
function workflow_actions_remove($tid, $aid) {
$ops = array();
foreach (workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
// Transition ID is the last part, e.g., foo-bar-1.
$transition = array_pop(explode('-', $data->hook));
if ($tid == $transition) {
$hooks[] = $data->hook;
}
}
foreach ($hooks as $hook) {
workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
foreach (workflow_actions_get_actions_by_aid($aid) as $action) {
watchdog('workflow', 'Action %action has been unassigned.',
array('%action' => $action->description));
}
}
}
/**
* DB functions.
*/
/**
* Get all trigger assingments for workflow.
*/
function workflow_actions_get_trigger_assignments() {
$results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow"');
return $results->fetchAll();
}
/**
* Get all trigger assignements for workflow and a given action.
*/
function workflow_actions_get_trigger_assignments_by_aid($aid) {
$results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow" AND aid = ":aid"', array(':aid' => $aid));
return $results->fetchAll();
}
/**
* Delete assignments, by action and operation.
*/
function workflow_actions_delete_trigger_assignments_by_aid_op($aid, $op) {
return db_delete('trigger_assignments')->condition('hook', 'workflow')->condition('hook', $op)->condition('aid', $aid)->execute();
}
/**
* Get a specific action.
*/
function workflow_actions_get_actions_by_aid($aid) {
$results = db_query('SELECT * FROM {actions} WHERE aid = ":aid"', array(':aid' => $aid));
return $results->fetchAll();
}
/**
* Get the actions associated with a given transition.
* Array of action ids in the same format as _trigger_get_hook_aids().
*/
function workflow_actions_get_actions_by_tid($tid) {
$aids = array();
foreach (workflow_actions_get_trigger_assignments() as $data) {
// Transition ID is the last part, e.g., foo-bar-1.
$transition = array_pop(explode('-', $data->hook));
if ($tid == $transition) {
// Specialized, TODO seprate this SQL out later
$results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa ' .
'LEFT JOIN {actions} a ON aa.aid = a.aid ' .
'WHERE aa.hook = ":hook" ' .
'ORDER BY weight', array(':hook' => $data->hook));
foreach ($results as $action) {
$aids[$action->aid]['type'] = $action->type;
}
}
}
return $aids;
}
/**
* Implementation of hook_drupal_alter().
*/
function workflow_actions_action_info_alter(&$info) {
$transitions = workflow_actions_trigger_info();
if (empty($transitions)) {
return;
}
foreach ((array)$transitions['workflow'] as $transition => $data) {
// Loop through all available node actions and add them as triggers.
// But not if this has been flagged as a creation state.
if ($data['workflow_creation_state'] != TRUE) {
foreach (node_action_info() as $action => $data) {
$info[$action]['triggers'][] = $transition;
}
}
// Either way, unset the creation flag so we don't confuse anyone later.
unset($transitions['workflow'][$transition]['workflow_creation_state']);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* @file
* Hooks provided by the workflow_admin_ui module.
*/
/**
* Implements hook_workflow_operations().
*
* @param $op
* 'top_actions': Allow modules to insert their own front page action links.
* 'operations': Allow modules to insert their own workflow operations.
* 'state': Allow modules to insert state operations.
* @param $workflow
* The current workflow object.
* @param $state
* The current state object.
*/
function hook_workflow($op, object $workflow, object $state) {
switch ($op) {
case 'top_actions':
$actions = array();
// The workflow_admin_ui module creates links to add a new state,
// and reach each workflow.
// Your module may add to these actions.
return $actions;
case 'operations':
$actions = array();
// The workflow_admin_ui module creates links to add a new state,
// edit the workflow, and delete the workflow.
// Your module may add to these actions.
return $actions;
case 'state':
$ops = array();
// The workflow_admin_ui module does not use this.
// Your module may add operations.
return $ops;
}
}

View File

@@ -0,0 +1,19 @@
/**
* @file
* Style Sheets for the Workflow_Admin_UI module.
*/
#workflow_admin_ui_overview .state-name input {
margin-right: 2em; /* Make room for dragging handle. */
}
#workflow_admin_ui_overview .state-status {
text-align: center;
}
#workflow_admin_ui_overview .state-count {
text-align: right;
}
#workflow_admin_ui_overview th.state-ops {
}

View File

@@ -0,0 +1,14 @@
name = Workflow UI
description = "Provides administrative UI for workflow."
package = Workflow
dependencies[] = workflow
core = 7.x
configure = admin/config/workflow/workflow
stylesheets[all][] = workflow_admin_ui.css
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,40 @@
<?php
/**
* @file
* Install, update and uninstall functions for the workflow_admin_ui module.
*
*/
/**
* Implements hook_enable().
*/
function workflow_admin_ui_enable() {
drupal_set_message(_workflow_admin_ui_participate());
}
/**
* Helper function. Used both by update and enable.
*/
function _workflow_admin_ui_participate() {
$perms = array('participate in workflow' => 1);
foreach (user_roles() as $rid => $name) {
user_role_change_permissions($rid, $perms);
}
return t('Please review which roles may participate in workflows. <a href="!url">Permissions</a>',
array('!url' => url('admin/people/permissions', array('fragment' => 'module-workflow_admin_ui'))));
}
/**
* Patch for #1540824 requires that the menu be rebuilt.
*/
function workflow_admin_ui_update_7000(&$sandbox) {
menu_rebuild();
return t('Workflow_admin_ui requested a menu rebuild.');
}
/**
* Give all user roles the ability to participate in workflows.
*/
function workflow_admin_ui_update_7001(&$sandbox) {
return _workflow_admin_ui_participate();
}

View File

@@ -0,0 +1,12 @@
name = Workflow Clean Up
description = "Cleans up Workflow cruft"
dependencies[] = workflow
core = 7.x
package = Workflow
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,210 @@
<?php
/**
* @file
* Cleans up Workflow cruft that may build up over time.
*/
/**
* Implements hook_menu().
*/
function workflow_cleanup_menu() {
$items = array();
$items['admin/config/workflow/workflow/cleanup'] = array(
'title' => 'Workflow Clean Up',
'access arguments' => array('administer workflow'),
'page callback' => 'drupal_get_form',
'page arguments' => array('workflow_cleanup_form'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_theme().
*/
function workflow_cleanup_theme() {
return array(
'workflow_cleanup_form' => array('render element' => 'form'),
);
}
/**
* Implements hook_workflow_operations().
* Might as well eat our own cooking.
*/
function workflow_cleanup_workflow_operations($op, $workflow = NULL, $state = NULL) {
switch ($op) {
case 'top_actions':
$workflows = workflow_get_workflows();
$alt = t('Clean up workflow cruft');
$actions = array(
'workflow-cleanup' => array(
'title' => t('Clean up'),
'href' => 'admin/config/workflow/workflow/cleanup',
'attributes' => array('alt' => $alt, 'title' => $alt),
),
);
return $actions;
}
}
/**
* The main cleanup page.
*/
function workflow_cleanup_form($form, $form_state) {
$bc = array(l(t('Home'), '<front>'));
$bc[] = l(t('Configuration'), 'admin/config');
$bc[] = l(t('Workflow'), 'admin/config/workflow');
$bc[] = l(t('Workflow'), 'admin/config/workflow/workflow');
drupal_set_breadcrumb($bc);
$form = array();
// Get all of the states, indexed by sid.
$states = $orphans = $inactive = array();
foreach (workflow_get_workflow_states() as $state) {
$states[$state->sid] = $state;
// Is it associated with a workflow?
if (empty($state->name)) {
$orphans[$state->sid] = $state->state;
}
else {
// Is it associated with a workflow?
if (!$state->status) {
$inactive[$state->sid] = $state->state;
}
}
}
// Deal with no orphan states.
if (!$orphans) {
$orphans[0] = t('None');
}
// Deal with no inactive states.
if (!$inactive) {
$inactive[0] = $states[0] = t('None');
}
$form['#workflow_states'] = $states;
$form['no_workflow'] = array(
'#type' => 'container',
'#title' => t('Orphaned States'),
'#description' => t('These states no longer belong to an existing workflow.'),
'#tree' => TRUE,
);
foreach ($orphans as $sid => $state) {
$form['no_workflow'][$sid]['check'] = array(
'#type' => 'checkbox',
'#return_value' => $sid,
);
$form['no_workflow'][$sid]['name'] = array(
'#type' => 'markup',
'#markup' => check_plain($state),
);
}
$form['inactive'] = array(
'#type' => 'container',
'#title' => t('Inactive (Deleted) States'),
'#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'] = array(
'#type' => 'checkbox',
'#return_value' => $sid,
);
$form['inactive'][$sid]['name'] = array(
'#type' => 'markup',
'#markup' => check_plain($state),
);
$form['inactive'][$sid]['wf'] = array(
'#type' => 'markup',
'#markup' => check_plain($states[$sid]->name),
);
}
$form['submit'] = array('#type' => 'submit', '#value' => t('Delete selected states'));
return $form;
}
/**
* Theme the main form.
*/
function theme_workflow_cleanup_form($variables) {
$form = $variables['form'];
$output = '';
$header = array(t('select'), t('State'));
$rows = array();
foreach (element_children($form['no_workflow']) as $sid) {
$rows[] = array(
drupal_render($form['no_workflow'][$sid]['check']),
drupal_render($form['no_workflow'][$sid]['name']),
);
}
$output .= '<h3>' . $form['no_workflow']['#title'] . '</h3>';
$output .= '<div class="description">' . $form['no_workflow']['#description'] . '</div>';
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;')));
$header[] = t('Workflow');
$rows = array();
foreach (element_children($form['inactive']) as $sid) {
$rows[] = array(
drupal_render($form['inactive'][$sid]['check']),
drupal_render($form['inactive'][$sid]['name']),
drupal_render($form['inactive'][$sid]['wf']),
);
}
$output .= '<h3>' . $form['inactive']['#title'] . '</h3>';
$output .= '<div class="description">' . $form['inactive']['#description'] . '</div>';
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;')));
$output .= drupal_render_children($form);
return $output;
}
/**
* Submission handler for main cleanup form.
*/
function workflow_cleanup_form_submit($form, $form_state) {
$states = $form['#workflow_states'];
foreach (array('no_workflow', 'inactive') as $section) {
foreach ($form_state['values'][$section] as $sid => $data) {
// FAPI returns either a 0 or the sid.
if ($data['check']) {
// Delete any transitions this state is involved in.
$trans_del = db_delete('workflow_transitions')->condition('target_sid', $sid)->execute();
$trans_del += db_delete('workflow_transitions')->condition('sid', $sid)->execute();
if ($trans_del) {
drupal_set_message(t('@count transitions for the "@state" state have been deleted.',
array('@state' => $states[$sid]->state, '@count' => $trans_del)));
}
// Remove history records too.
$hist_del = db_delete('workflow_node_history')->condition('sid', $sid)->execute();
if ($hist_del) {
drupal_set_message(t('@count history records for the "@state" state have been deleted.',
array('@state' => $states[$sid]->state, '@count' => $hist_del)));
}
// Go ahead and delete the state.
db_delete('workflow_states')->condition('sid', $sid)->execute();
drupal_set_message(t('The "@state" state has been deleted.', array('@state' => $states[$sid]->state)));
}
}
}
}

View File

@@ -0,0 +1,12 @@
name = Workflow Revert
description = "Adds an 'Revert' link to the first workflow history row."
dependencies[] = workflow
core = 7.x
package = Workflow
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,106 @@
<?php
/**
* @file
* Adds an 'Revert' link to the first workflow history row.
*/
/**
* Implements hook_permission().
*/
function workflow_revert_permission() {
return array(
'revert workflow' => array(
'title' => t('Revert workflow'),
'description' => t('Allow user to revert workflow state.'),
),
);
}
/**
* Implements hook_menu().
*/
function workflow_revert_menu() {
$items = array();
$items['workflow_revert'] = array(
'title' => 'Workflow Undo',
'access arguments' => array('revert workflow'),
'page callback' => 'drupal_get_form',
'page arguments' => array('workflow_revert_form'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Menu callback to do the revert function.
*/
function workflow_revert_form($form, $form_state, $nid = NULL, $sid = NULL) {
$args = array('#nid' => $nid, '#sid' => $sid);
if (drupal_valid_token($_GET['token'], 'workflow_revert ' . $sid)) {
$states = workflow_get_workflow_states_all();
$node = node_load($nid);
$args['#node'] = $node;
$question = t('Are you sure you want to revert %title to the "@state" state?', array(
'@state' => $states[$sid],
'%title' => $node->title,
));
return confirm_form($args,
$question,
"node/$nid",
t('The worflow state will be changed.')
);
}
else {
watchdog('workflow_revert', 'Invalid token', array(), WATCHDOG_ERROR);
drupal_set_message(t('Invalid token. Your information has been recorded.'), 'error');
drupal_goto("node/$nid");
}
}
function workflow_revert_form_submit($form, $form_state) {
$node = $form['#node'];
$comment = t('State reverted');
$sid = $form['#sid'];
// Force the transition because it's probably not valid.
workflow_execute_transition($node, $sid, $comment, TRUE);
drupal_set_message(t('Workflow reverted.', array()));
drupal_goto('node/' . $form['#nid'] . '/workflow');
}
/**
* Implements hook_workflow_history_alter().
* Add an 'undo' operation for the most recent history change.
*
* @param $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_node_history table.
*
* If you want to add additional data, place it in the 'extra' value.
*/
function workflow_revert_workflow_history_alter(&$variables) {
static $first = TRUE;
// Only mark the first row.
if ($first) {
// Let's ask other modules if the reversion is allowed.
$node = node_load($variables['history']->nid);
$result = module_invoke_all('workflow', 'transition permitted', $variables['sid'], $variables['old_sid'], $node);
// Did anybody veto this choice?
if (!in_array(FALSE, $result)) {
// If not vetoed, mark it.
$options = array('query' => array('token' => drupal_get_token('workflow_revert ' . $variables['old_sid'])));
$path = 'workflow_revert/' . $variables['history']->nid . '/' . $variables['old_sid'];
$variables['extra'] = l('Revert state change', $path, $options);
}
// That was your one chance...
$first = FALSE;
}
}

View File

@@ -0,0 +1,12 @@
name = Workflow rules
description = Provides rules integration for workflows.
dependencies[] = workflow
dependencies[] = rules
package = Workflow
core = 7.x
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,35 @@
<?php
/**
* @file
* Provide rules for workflows.
* Why it's own module? Some sites prefer rules, some prefer actions,
* all prefer a lower code footprint and better performance.
* Additional creadit to gcassie ( http://drupal.org/user/80260 ) for
* the initial push to split rules out of core workflow.
*/
/**
* Implements hook_workflow().
*
* @param $op
* The current workflow operation: 'transition pre' or 'transition post'.
* @param $old_state
* The state ID of the current state.
* @param $new_state
* The state ID of the new state.
* @param $node
* The node whose workflow state is changing.
*/
function workflow_rules_workflow($op, $old_state, $new_state, $node) {
switch ($op) {
case 'transition post':
// Rules are updated on after the transition.
if ($old_state == $new_state) {
rules_invoke_event('workflow_comment_added', $node, $old_state, $new_state);
return;
}
rules_invoke_event('workflow_state_changed', $node);
break;
}
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* @file
* Rules integration for the Workflow module
*/
/**
* Implements hook_rules_event_info().
*/
function workflow_rules_rules_event_info() {
$events = array(
'workflow_state_changed' => array(
'group' => t('Workflow'),
'label' => t('Workflow state has changed'),
'variables' => rules_events_node_variables(t('updated content'), TRUE),
),
'workflow_comment_added' => array(
'group' => t('Workflow'),
'label' => t('Workflow comment was added, but state did not change'),
'variables' => rules_events_node_variables(t('updated content'), TRUE),
),
);
return $events;
}
/**
* Implements hook_rules_condition_info().
*/
function workflow_rules_rules_condition_info() {
return array(
'workflow_check_transition' => array(
'group' => t('Workflow'),
'label' => t('Check workflow transition'),
'parameter' => array(
'node' => array(
'type' => 'node',
'label' => t('Node'),
'description' => t('The node whose workflow state is being checked.'),
),
'old_state' => array(
'type' => 'list<integer>',
'label' => t('Old workflow state'),
'options list' => '_workflow_rules_condition_select',
'description' => t('The workflow state moved from.'),
),
'new_state' => array(
'type' => 'list<integer>',
'label' => t('New workflow state'),
'options list' => '_workflow_rules_condition_select',
'description' => t('The workflow state moved to.'),
),
),
),
'workflow_check_state' => array(
'group' => t('Workflow'),
'label' => t('Content has a workflow state'),
'parameter' => array(
'node' => array(
'type' => 'node',
'label' => t('Node'),
'description' => t('The node to compare the current workflow state of.'),
),
'workflow_state' => array(
'type' => 'list<integer>',
'label' => t('Compare workflow state'),
'options list' => '_workflow_rules_condition_select',
'description' => t('The possible workflow states to compare against.'),
),
),
),
'workflow_check_previous_state' => array(
'group' => t('Workflow'),
'label' => t('Content has a previous workflow state'),
'parameter' => array(
'node' => array(
'type' => 'node',
'label' => t('Node'),
'description' => t('The node to compare the previous workflow state of.'),
),
'workflow_state' => array(
'type' => 'list<integer>',
'label' => t('Compare workflow state'),
'options list' => '_workflow_rules_condition_select',
'description' => t('The possible workflow states to compare against.'),
),
),
),
);
}
/**
* Implements hook_rules_action_info().
*/
function workflow_rules_rules_action_info() {
return array(
'workflow_rules_set_state' => array(
'group' => t('Workflow'),
'label' => t('Set workflow state for content'),
'parameter' => array(
'node' => array(
'type' => 'node',
'label' => t('Node'),
'description' => t('The node to set the current workflow state of.'),
// 'save' => TRUE,
),
'workflow_state' => array(
'type' => 'list<integer>',
'label' => t('New workflow state'),
'options list' => '_workflow_rules_action_select',
'description' => t('The workflow state to set (select only one).'),
),
'workflow_comment' => array(
'type' => 'text',
'label' => t('Workflow Comment'),
'description' => t('The workflow comment to set.'),
'optional' => TRUE,
),
),
),
);
}
/**
* Condition callback: gather all workflow states.
*/
function _workflow_rules_condition_select() {
$options['ANY'] = 'ANY state';
foreach (workflow_get_workflows() as $workflow) {
foreach (workflow_get_workflow_states_by_wid($workflow->wid) as $state) {
$states[$state->sid] = check_plain($workflow->name) . ': ' . check_plain($state->state);
}
}
$options = $options + $states;
return $options;
}
/**
* Condition callback: gather all workflow states.
*/
function _workflow_rules_action_select() {
foreach (workflow_get_workflows() as $workflow) {
foreach (workflow_get_workflow_states_by_wid($workflow->wid) as $state) {
$states[$state->sid] = check_plain($workflow->name) . ': ' . check_plain($state->state);
}
}
return $states;
}
/**
* Condition implementation: check state transition.
*/
function workflow_check_transition($node, $old_states, $new_states) {
$node_current_state = workflow_node_current_state($node);
$node_old_state = workflow_node_previous_state($node);
if (in_array('ANY', $old_states)) {
if (in_array('ANY', $new_states)) {
return TRUE;
}
return in_array($node_current_state, $new_states);
}
if (in_array('ANY', $new_states)) {
return in_array($node_old_state, $old_states);
}
return in_array($node_old_state, $old_states) && in_array($node_current_state, $new_states);
}
/**
* Condition implementation: check current state.
*/
function workflow_check_state($node, $states) {
$node_state = workflow_node_current_state($node);
return workflow_check_given_state($node, $states, $node_state);
}
/**
* Condition implementation: check previous state.
*/
function workflow_check_previous_state($node, $states) {
$node_state = workflow_node_previous_state($node);
return workflow_check_given_state($node, $states, $node_state);
}
/**
* Condition implementation helper function: check given state.
*/
function workflow_check_given_state($node, $states, $node_state) {
if (in_array('ANY', $states)) {
return TRUE;
}
if (in_array($node_state, $states)) {
return TRUE;
}
return FALSE;
}
/**
* Action implementation: set current state, ignoring current user permissione.
*/
function workflow_rules_set_state($node, $states, $comment = NULL) {
// Select the last state on the list.
$sid = array_pop($states);
if (!empty($comment)) {
$node->workflow_comment = $comment;
}
workflow_transition($node, $sid, TRUE);
unset($node->workflow_comment);
}

View File

@@ -0,0 +1,13 @@
name = Workflow Search API
description = "Adds workflow state information to Search API index"
dependencies[] = workflow
dependencies[] = entity
core = 7.x
package = Workflow
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,33 @@
<?php
/**
* @file
* Adds workflow state information to Search API index.
*/
/**
* Implements hook_entity_property_info_alter().
*/
function workflow_search_api_entity_property_info_alter(&$info) {
$info['node']['properties']['workflow_state_name'] = array(
'type' => 'text',
'label' => t('Workflow state name'),
'sanitized' => TRUE,
'getter callback' => 'workflow_search_api_property_workflow_state_getter_callback',
);
}
/**
* Getter callback for workflow state defined in workflow_search_api_entity_property_info_alter.
*/
function workflow_search_api_property_workflow_state_getter_callback($item) {
if ($item->workflow) {
// Get text value of workflow state.
$state = workflow_get_workflow_states_by_sid($item->workflow);
$state_name = $state->state;
}
else {
$state_name = '';
}
return $state_name;
}

View File

@@ -0,0 +1,37 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
INTRODUCTION
------------
There is an important issue to keep in mind as you use action hooks and workflow!!
If the machine readable name of the content type on which you want to define actions
in the workflow exceeds 20 characters then the actions you define will not be visible
in the screen where you define the triggers nor will they execute.
The reason is that the length of the field "op" in the "trigger-assignments" table
is 32 characters. The name of this "op"-field is a concatenation of the string
"workflow-" with the machine readable name of the content type, another "-" and the
transition-id on which the action has to be performed. If the latter has a length of
1 then this leaves 32 - 9 - 1 - 1 = 21 characters for the machine readable name of
the content type.
So: KEEP YOUR CONTENT TYPE NAMES SHORT.
Unfortunately the code that handles this is in core, so not readily changable. If
you have trouble seeing your actions check your name lengths.
See further discussion at:
http://drupal.org/node/585726
See request put to core to make the change at:
http://drupal.org/node/1062068
Closed and told to have workflow make the table change ourselves. Given that changing
name lengths haphazardly would spread the bugs around even more this approach was
not followed.

View File

@@ -0,0 +1,12 @@
name = Workflow VBO
description = Provides workflow actions for VBO.
dependencies[] = workflow
dependencies[] = views_bulk_operations
package = Workflow
core = 7.x
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,142 @@
<?php
/**
* @file
* Provide workflow actions for VBO.
* Split out from workflow_actions.
*/
/**
* Implements hook_action_info().
*/
function workflow_vbo_action_info() {
return array(
'workflow_vbo_next_state_action' => array(
'type' => 'node',
'label' => t('Change workflow state of post to next state'),
'configurable' => FALSE,
'triggers' => array('any'),
),
'workflow_vbo_given_state_action' => array(
'type' => 'node',
'label' => t('Change workflow state of post to new state'),
'configurable' => TRUE,
'triggers' => array('any'),
),
);
}
/**
* Implements a Drupal action. Move a node to the next state in the workfow.
*/
function workflow_vbo_next_state_action($node, $context) {
// If this action is being fired because it's attached to a workflow transition
// then the node's new state (now its current state) should be in $node->workflow
// because that is where the value from the workflow form field is stored;
// otherwise the current state is placed in $node->workflow by our nodeapi load.
if (!isset($node->nid)) {
watchdog('workflow_vbo', 'Unable to get current node id state of node - node is not yet saved.');
return;
}
if (!isset($node->workflow)) {
watchdog('workflow_vbo', 'Unable to get current workflow state of node %nid.',
array('%nid' => $node->nid));
return;
}
$current_state = $node->workflow;
$new_state = $current_state;
// Get the node's new state.
$choices = workflow_field_choices($node);
foreach ($choices as $sid => $name) {
if (isset($flag)) {
$new_state = $sid;
$new_state_name = $name;
break;
}
if ($sid == $current_state) {
$flag = TRUE;
}
}
// Fire the transition.
workflow_execute_transition($node, $new_state);
}
/**
* Implements a Drupal action. Move a node to a specified state.
*/
function workflow_vbo_given_state_action($node, $context) {
global $user;
if (!isset($node->nid)) {
watchdog('workflow_vbo', 'Unable to get current node id state of node - node is not yet saved.');
return;
}
$comment = t($context['workflow_comment'], array(
'%title' => check_plain($node->title),
'%state' => check_plain($context['state_name']),
'%user' => theme('username', array('account' => $user)),
));
workflow_execute_transition($node, $context['target_sid'], $comment, $context['force']);
}
/**
* Configuration form for "Change workflow state of post to new state" action.
*
* @see workflow_vbo_given_state_action()
*/
function workflow_vbo_given_state_action_form($context) {
$previous_workflow = '';
$options = array();
// Get all states, only where active.
foreach (workflow_get_workflow_states(array('status' => 1)) as $data) {
$options[$data->name][$data->sid] = check_plain($data->state);
}
$form['target_sid'] = array(
'#type' => 'select',
'#title' => t('Target state'),
'#description' => t('Please select that state that should be assigned when this action runs.'),
'#default_value' => isset($context['target_sid']) ? $context['target_sid'] : '',
'#options' => $options,
);
$form['force'] = array(
'#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.'),
'#default_value' => isset($context['force']) ? $context['force'] : '',
);
$form['workflow_comment'] = array(
'#type' => 'textfield',
'#title' => t('Message'),
'#description' => t('This message will be written into the workflow history log when the action ' .
'runs. You may include the following variables: %state, %title, %user'),
'#default_value' => isset($context['workflow_history']) ? $context['workflow_history'] : t('Action set %title to %state by %user.'),
);
return $form;
}
/**
* Submit handler for "Change workflow state of post to new state" action
* configuration form.
*
* @see workflow_vbo_given_state_action_form()
*/
function workflow_vbo_given_state_action_submit($form_id, $form_state) {
if ($state = workflow_get_workflow_states_by_sid($form_state['values']['target_sid'])) {
return array(
'target_sid' => $form_state['values']['target_sid'],
'state_name' => check_plain($state->state),
'force' => $form_state['values']['force'],
'workflow_comment' => $form_state['values']['workflow_comment'],
);
}
}

View File

@@ -0,0 +1,259 @@
<?php
/**
* @file
* Provide views data and handler information for workflow.module.
*/
/**
* @defgroup views_workflow_module workflow.module handlers
*
* Includes the 'workflow_node' and 'workflow_state' tables, but generates a
* pseudo-table for each separate workflow that's been set up.
* @{
*/
/**
* Implements hook_views_data().
*/
function workflow_views_views_data() {
// Workflow states.
$data['workflow_states']['table']['group'] = t('Workflow');
$data['workflow_states']['table']['join'] = array(
'node' => array(
'field' => 'sid',
'left_table' => 'workflow_node',
'left_field' => 'sid',
),
);
$data['workflow_states']['weight'] = array(
'title' => t('State weight'),
'help' => t('The weight of the current workflow state that the node is in.'),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
$data['workflow_states']['state'] = array(
'title' => t('Current state name'),
'help' => t('The readable name of the workflow state that the node is in. (Less efficient, use only when click-sorting by state name.)'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
);
// Workflow node.
$data['workflow_node']['table']['group'] = t('Workflow');
$data['workflow_node']['table']['join'] = array(
'node' => array(
'field' => 'nid',
'left_table' => 'node',
'left_field' => 'nid',
),
);
$data['workflow_node']['sid'] = array(
'title' => t('Current state'),
'help' => t('The current workflow state that the node is in.'),
'field' => array(
'handler' => 'workflow_views_handler_field_sid',
'click sortable' => TRUE,
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
'click sortable' => TRUE,
'numeric' => TRUE,
'name table' => 'workflow_states',
'name field' => 'state',
),
'filter' => array(
'handler' => 'workflow_views_handler_filter_sid',
'numeric' => TRUE,
),
);
$data['workflow_node']['stamp'] = array(
'title' => t('Current time'),
'help' => t('The time at which the node moved to the current state.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_date',
'numeric' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
);
$data['workflow_node']['uid'] = array(
'title' => t('Last changed user'),
'help' => t('The user who performed the last state change.'),
'relationship' => array(
'base' => 'users',
'base field' => 'uid',
'handler' => 'views_handler_relationship',
'label' => t('User'),
),
'argument' => array(
'handler' => 'views_handler_argument_user_uid',
'click sortable' => TRUE,
'name table' => 'workflow_node',
'name field' => 'uid',
),
'filter' => array(
'handler' => 'views_handler_filter_user_name',
'numeric' => TRUE,
'name table' => 'workflow_node',
'name field' => 'uid',
),
);
$data['workflow_node_current']['table']['group'] = t('Workflow');
// Explain how this table joins to others.
$data['workflow_node_current']['table']['join'] = array(
'node' => array(
'table' => 'workflow_node_history',
'field' => 'nid',
'left_table' => 'workflow_node',
'left_field' => 'nid',
'extra' => 'workflow_node.stamp = workflow_node_current.stamp AND workflow_node.nid = workflow_node_current.nid',
),
);
// Workflow node current comment.
$data['workflow_node_current']['comment'] = array(
'title' => t('Current comment'),
'help' => t('The comment describing why the node was moved from the last state to the current state.'),
'field' => array(
'handler' => 'views_handler_field_xss',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_string',
'numeric' => TRUE,
),
);
// Workflow scheduled transition.
$data['workflow_scheduled_transition']['table']['group'] = t('Workflow');
$data['workflow_scheduled_transition']['table']['join'] = array(
'node' => array(
'field' => 'nid',
'left_table' => 'node',
'left_field' => 'nid',
),
);
$data['workflow_scheduled_transition']['sid'] = array(
'title' => t('Scheduled state'),
'help' => t('The current workflow state that the node is in.'),
'field' => array(
'handler' => 'workflow_views_handler_field_sid',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'workflow_views_handler_filter_sid',
'numeric' => TRUE,
),
);
$data['workflow_scheduled_transition']['scheduled'] = array(
'title' => t('Scheduled time'),
'help' => t('The time at which the node will change workflow states.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_date',
'numeric' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
);
$data['workflow_scheduled_transition']['comment'] = array(
'title' => t('Scheduled comment'),
'help' => t('A comment describing why the node was scheduled for state transition.'),
'field' => array(
'handler' => 'views_handler_field_xss',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_string',
'numeric' => TRUE,
),
);
// Workflow node history.
$data['workflow_node_history']['table']['group'] = t('Workflow');
$data['workflow_node_history']['table']['join'] = array(
'node' => array(
'field' => 'nid',
'left_table' => 'node',
'left_field' => 'nid',
),
);
$data['workflow_node_history']['sid'] = array(
'title' => t('Previous state'),
'help' => t('A workflow state that the node was in previously.'),
'field' => array(
'handler' => 'workflow_views_handler_field_sid',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'workflow_views_handler_filter_sid',
'numeric' => TRUE,
),
);
$data['workflow_node_history']['stamp'] = array(
'title' => t('Previous time'),
'help' => t('The time at which the node moved from one state to another.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_date',
'numeric' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
);
$data['workflow_node_history']['comment'] = array(
'title' => t('Previous comment'),
'help' => t('A comment describing why the node was moved from one state to another in the past.'),
'field' => array(
'handler' => 'views_handler_field_xss',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'views_handler_filter_string',
'numeric' => TRUE,
),
);
$data['workflow_node_history']['uid'] = array(
'title' => t('Previous comment author'),
'help' => t('The author of a comment describing why the node was moved from one state to another in the past.'),
'field' => array(
'handler' => 'workflow_views_handler_field_username',
'click sortable' => TRUE,
),
'relationship' => array(
'title' => t('Author'),
'help' => t("The User ID of the comment's author."),
'base' => 'users',
'base field' => 'uid',
'handler' => 'views_handler_relationship',
'label' => t('author'),
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
'filter' => array(
'handler' => 'views_handler_filter_user_name',
),
);
return $data;
}
/**
* @}
*/

View File

@@ -0,0 +1,245 @@
<?php
/**
* @file
* View for listing nodes by workflow state.
*/
/**
* Implements hook_views_default_views().
*/
function workflow_views_views_default_views() {
$view = new view;
$view->name = 'workflow_summary';
$view->description = 'See which posts are in which workflow state.';
$view->tag = 'workflow';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('fields', array(
'sid' => array(
'label' => 'Current state',
'exclude' => 0,
'id' => 'sid',
'table' => 'workflow_node',
'field' => 'sid',
'relationship' => 'none',
),
'title' => array(
'label' => 'Title',
'link_to_node' => 1,
'exclude' => 0,
'id' => 'title',
'table' => 'node',
'field' => 'title',
'relationship' => 'none',
),
'type' => array(
'label' => 'Type',
'link_to_node' => 0,
'exclude' => 0,
'id' => 'type',
'table' => 'node',
'field' => 'type',
'relationship' => 'none',
),
));
$handler->override_option('filters', array(
'sid' => array(
'operator' => 'in',
'value' => array(),
'group' => '0',
'exposed' => TRUE,
'expose' => array(
'use_operator' => 0,
'operator' => 'sid_op',
'identifier' => 'sid',
'label' => 'Current State',
'optional' => 1,
'single' => 1,
'remember' => 0,
'reduce' => 0,
),
'id' => 'sid',
'table' => 'workflow_node',
'field' => 'sid',
'relationship' => 'none',
),
'type' => array(
'operator' => 'in',
'value' => array(),
'group' => '0',
'exposed' => TRUE,
'expose' => array(
'use_operator' => 0,
'operator' => 'type_op',
'identifier' => 'type',
'label' => 'Content Type',
'optional' => 1,
'single' => 1,
'remember' => 0,
'reduce' => 0,
),
'id' => 'type',
'table' => 'node',
'field' => 'type',
'relationship' => 'none',
),
));
$handler->override_option('access', array(
'type' => 'perm',
'perm' => 'access workflow summary views',
));
$handler->override_option('title', 'Workflow summary');
$handler->override_option('items_per_page', 25);
$handler->override_option('use_pager', '1');
$handler->override_option('style_plugin', 'table');
$handler->override_option('style_options', array(
'grouping' => '',
'override' => 1,
'sticky' => 0,
'order' => 'asc',
'columns' => array(
'state' => 'state',
'title' => 'title',
),
'info' => array(
'state' => array(
'sortable' => 1,
'separator' => '',
),
'title' => array(
'sortable' => 0,
'separator' => '',
),
),
'default' => 'state',
));
$handler = $view->new_display('page', 'Page', 'page_1');
$handler->override_option('path', 'workflow/summary');
$handler->override_option('menu', array(
'type' => 'default tab',
'title' => 'Summary',
'weight' => '-1',
));
$handler->override_option('tab_options', array(
'type' => 'normal',
'title' => 'Workflow summary',
'weight' => '0',
));
$views[$view->name] = $view;
$view = new view;
$view->name = 'workflow_pending';
$view->description = 'Shows upcoming state changes.';
$view->tag = 'workflow';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('fields', array(
'title' => array(
'label' => 'Title',
'link_to_node' => 1,
'exclude' => 0,
'id' => 'title',
'table' => 'node',
'field' => 'title',
'relationship' => 'none',
),
'sid' => array(
'label' => 'Current state',
'exclude' => 0,
'id' => 'sid',
'table' => 'workflow_node',
'field' => 'sid',
'relationship' => 'none',
),
'sid_1' => array(
'label' => 'Scheduled state',
'exclude' => 0,
'id' => 'sid_1',
'table' => 'workflow_scheduled_transition',
'field' => 'sid',
'relationship' => 'none',
),
'scheduled' => array(
'label' => 'Scheduled time',
'date_format' => 'small',
'custom_date_format' => '',
'exclude' => 0,
'id' => 'scheduled',
'table' => 'workflow_scheduled_transition',
'field' => 'scheduled',
'relationship' => 'none',
),
'comment' => array(
'label' => 'Scheduled comment',
'exclude' => 0,
'id' => 'comment',
'table' => 'workflow_scheduled_transition',
'field' => 'comment',
'relationship' => 'none',
),
));
$handler->override_option('sorts', array(
'scheduled' => array(
'order' => 'ASC',
'granularity' => 'second',
'id' => 'scheduled',
'table' => 'workflow_scheduled_transition',
'field' => 'scheduled',
'relationship' => 'none',
),
));
$handler->override_option('filters', array(
'scheduled' => array(
'operator' => '>=',
'value' => array(
'type' => '',
'value' => '-1 hour',
'min' => '-1 hour',
'max' => '',
),
'group' => '0',
'exposed' => FALSE,
'expose' => array(
'operator' => FALSE,
'label' => '',
),
'id' => 'scheduled',
'table' => 'workflow_scheduled_transition',
'field' => 'scheduled',
'relationship' => 'none',
),
));
$handler->override_option('access', array(
'type' => 'perm',
'perm' => 'access workflow summary views',
));
$handler->override_option('title', 'Workflow Pending Changes');
$handler->override_option('items_per_page', 25);
$handler->override_option('use_pager', '1');
$handler->override_option('style_plugin', 'table');
$handler = $view->new_display('page', 'Page', 'page_1');
$handler->override_option('path', 'workflow/pending');
$handler->override_option('menu', array(
'type' => 'tab',
'title' => 'Pending',
'weight' => '0',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => '',
'weight' => 0,
));
$views[$view->name] = $view;
return $views;
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* @file
* Provide views argument handler for workflow.module.
*/
/**
* Argument handler to accept a node type.
*/
class views_handler_argument_workflow_state extends views_handler_argument {
function construct() {
parent::construct('type');
}
/**
* Override the behavior of summary_name(). Get the user-friendly version
* of the workflow state.
*/
function summary_name($data) {
return $this->workflow_states($data->{$this->name_alias});
}
/**
* Override the behavior of title(). Get the user-friendly version of the
* workflow state.
*/
function title() {
return $this->workflow_states($this->argument);
}
/**
* Helper function, to gather the workflow name from a given arguement.
*/
function workflow_states($sid) {
if (empty($sid)) {
return t('No state');
}
static $states;
if (!isset($states)) {
// Get all, even the non-active states, filter by sid.
foreach (workflow_get_workflow_states() as $state) {
$states[$state->sid] = check_plain($state->state);
}
}
$output = $states[$sid];
if (empty($output)) {
$output = t('No state');
}
return check_plain($output);
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* @file
* Provide views field handler for workflow.module.
*/
/**
* Field handler to provide simple status name or renderer.
*/
class workflow_views_handler_field_sid extends views_handler_field {
function option_definition() {
$options = parent::option_definition();
$options['value'] = array('default' => FALSE, 'bool' => TRUE);
return $options;
}
function options_form(&$form, &$form_state) {
$form['value'] = array(
'#title' => t('Display value'),
'#description' => t('If checked, row field value will be displayed.'),
'#type' => 'checkbox',
'#default_value' => $this->options['value'],
);
parent::options_form($form, $form_state);
}
function render($values) {
if ($this->options['value']) {
if (empty($values->{$this->field_alias})) {
return NULL;
}
return $values->{$this->field_alias};
}
else {
if (empty($values->{$this->field_alias})) {
return t('No state');
}
static $states;
if (!isset($states)) {
$states = array();
foreach (workflow_get_workflow_states() as $state) {
$states[$state->sid] = $state->state;
}
}
$output = t($states[$values->{$this->field_alias}]);
if (empty($output)) {
$output = t('Unknown state');
}
return check_plain($output);
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
// $Id$
/**
* @file
* Provide views field handler for workflow.module.
*/
/**
* Field handler to allow linking to a user account
*/
class workflow_views_handler_field_username extends views_handler_field {
/**
* Override init function to add uid field.
*/
function init(&$view, &$data) {
parent::init($view, $data);
$this->additional_fields['uid'] = 'uid';
}
function option_definition() {
$options = parent::option_definition();
$options['link_to_user'] = array('default' => TRUE);
return $options;
}
function options_form(&$form, &$form_state) {
$form['link_to_user'] = array(
'#title' => t("Link this field to its user"),
'#type' => 'checkbox',
'#default_value' => $this->options['link_to_user'],
);
parent::options_form($form, $form_state);
}
function render_link($data, $values) {
if (!empty($this->options['link_to_user']) && $this->get_value($values, 'uid') > 0) {
$account = user_load($this->get_value($values, 'uid'));
return theme('username', array(
'account' => $account
));
}
else {
return $data;
}
}
function render($values) {
$value = $this->get_value($values);
return $this->render_link($this->sanitize_value($value), $values);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* @file
* Provide views filter handler for workflow.module.
*/
/**
* Filter by state.
*/
class workflow_views_handler_filter_sid extends views_handler_filter_in_operator {
var $value_form_type = 'select';
function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Workflow state');
$workflows = array();
foreach (workflow_get_workflows() as $data) {
$workflows[$data->wid] = check_plain(t($data->name));
}
if (!isset($workflows) || empty($workflows)) {
$this->value_options = array();
return;
}
$options = array('status' => 1);
if (count($workflows) > 1) {
$states = array('' => t('No state'));
foreach ($workflows as $wid => $name) {
foreach (workflow_get_workflow_states_by_wid($wid, $options) as $state) {
$states[$state->sid] = check_plain($name . ': ' . $state->state);
}
}
}
else {
foreach (workflow_get_workflow_states($options) as $state) {
$states[$state->sid] = check_plain($state->state);
}
}
$this->value_options = $states;
}
}
function query() {
$value = $this->is_a_group() && !$this->options['expose']['multiple'] ? drupal_array_merge_deep_array($this->value) : $this->value;
if (empty($value)) {
return;
}
$this->ensure_my_table();
$placeholder = !empty($this->definition['numeric']) ? '%d' : "'%s'";
if (count($value) == 1) {
$this->operator = ($this->operator == 'in') ? '= ' : '!= ';
$in = !empty($this->definition['numeric']) ? '%d' : "'%s'";
}
if ($this->operator == 'empty' || $this->operator == 'not empty') {
$value = NULL;
if ($this->operator == 'empty') {
$this->operator = "IS NULL";
}
else {
$this->operator = "IS NOT NULL";
}
}
$this->query->add_where($this->options['group'], $this->table_alias . '.' . $this->real_field, $value, $this->operator);
}
}

View File

@@ -0,0 +1,19 @@
name = Workflow views
description = Provides views integration for workflows.
package = Workflow
core = 7.x
dependencies[] = workflow
dependencies[] = views
files[] = includes/workflow_views.views.inc
files[] = includes/workflow_views.views_default.inc
files[] = includes/workflow_views_handler_filter_sid.inc
files[] = includes/workflow_views_handler_field_sid.inc
files[] = includes/workflow_views_handler_field_username.inc
files[] = includes/workflow_views_handler_argument_state.inc
; Information added by drupal.org packaging script on 2013-07-04
version = "7.x-1.2"
core = "7.x"
project = "workflow"
datestamp = "1372980654"

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Provide views integration for workflows.
* Why it's own module? Some sites have views some don't,
* all prefer a lower code footprint and better performance.
*/
/**
* Implements hook_permission().
*/
function workflow_views_permission() {
return array(
'access workflow summary views' => array(
'title' => t('Access workflow summary views'),
'description' => t('Access workflow summary views.'),
),
);
}
/**
* Implements hook_views_api().
*/
function workflow_views_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'workflow_views') . '/includes',
);
}