-
Notifications
You must be signed in to change notification settings - Fork 157
documentation for publish workflow #171
Changes from 4 commits
eb8c14a
88a48ba
c60d5ea
df62e7f
5dc3597
179b5c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,37 +23,328 @@ Configuration | |
|
||
# app/config/config.yml | ||
cmf_core: | ||
document_manager_name: default | ||
role: IS_AUTHENTICATED_ANONYMOUSLY # used by the publish workflow checker | ||
document_manager_name: null # used for the twig functions to fetch documents | ||
publish_workflow: | ||
enabled: true | ||
view_non_published_role: ROLE_CAN_VIEW_NON_PUBLISHED | ||
request_listener: true | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/config.xml --> | ||
<!-- role: used by the publish workflow checker --> | ||
<config xmlns="http://symfony.com/schema/dic/core" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. symfony.com => cmf.symfony.com |
||
document-manager-name="default" | ||
role="IS_AUTHENTICATED_ANONYMOUSLY" | ||
/> | ||
document-manager-name="null"> | ||
<publish-workflow | ||
enabled="true" | ||
view-non-published-role="ROLE_CAN_VIEW_NON_PUBLISHED" | ||
request-listener="true" | ||
/> | ||
<config/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
.. code-block:: php | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
$container->loadFromExtension('cmf_core', array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be indented one level back |
||
'document_manager_name' => null, | ||
'publish_workflow' => array( | ||
'enabled' => true, | ||
'view_non_published_role' => 'ROLE_CAN_VIEW_NON_PUBLISHED', | ||
'request_listener' => true, | ||
), | ||
)); | ||
|
||
The publish workflow is enabled by default. If you do not want to use it, you | ||
can set ``cmf_core.publish_workflow.enabled: false`` to gain some performance. | ||
|
||
.. _bundle-core-publish_workflow: | ||
|
||
Publish Workflow Checker | ||
------------------------ | ||
Publish Workflow | ||
---------------- | ||
|
||
The Bundle provides a ``cmf_core.publish_workflow_checker`` service | ||
which implements ``PublishWorkflowCheckerInterface``. This interface defines a | ||
single method ``checkIsPublished()``. | ||
The publish workflow system allows to control what content is available on the | ||
site. This is similar to the `Symfony2 security component`_. But contrary to the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
security context, the publish check can be executed even when no firewall is in | ||
place and the security context thus has no token (see `Symfony2 Authorization`_). | ||
|
||
.. code-block:: php | ||
The publish workflow is also tied into the security workflow: The core bundle | ||
registers a security voter that forwards security checks to the publish | ||
workflow. This means that if you always have a firewall, you can just use | ||
the normal security context and the twig function ``is_granted`` to check for | ||
publication. | ||
|
||
$publishWorkflowChecker = $container->get('cmf_core.publish_workflow_checker'); | ||
A good introduction to the Symfony core security is the `Security Chapter`_ in | ||
the Symfony2 book. | ||
|
||
// if to ignore the role when deciding if to consider the document as published | ||
$ignoreRole = false; | ||
|
||
if ($publishWorkflowChecker->checkIsPublished($document, $ignoreRole)) { | ||
Check if content is published | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The Bundle provides the ``cmf_core.publish_workflow.checker`` service which | ||
implements the :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface` | ||
of the Symfony security component. The method to check publication is, like | ||
with the security context, | ||
:method:`Symfony\\Component\\Security\\Core\\SecurityContextInterface::isGranted()``. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. double backticks on the end should be single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and you must remove |
||
|
||
This method is used as when doing `ACL checks`_: The first argument is the | ||
desired action, the second the content object you want to do the action on. | ||
|
||
In 1.0, the only actions supported by the default voters are ``VIEW`` and | ||
``VIEW_ANONYMOUS``. Having the right to view means that the current user is | ||
allowed to see this content either because it is published or because of his | ||
specific permissions. In some contexts, your application might not want to | ||
show unpublished content even to a privileged user so as not to confuse him. | ||
For this, the "view anonymous" permission is used. | ||
|
||
The workflow checker is configured with a role that is allowed to bypass | ||
publication checks so that it can see unpublished content. This role should be | ||
given to editors. The default name of the role is ``ROLE_CAN_VIEW_NON_PUBLISHED``. | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# app/config/security.yml | ||
security: | ||
role_hierarchy: | ||
ROLE_EDITOR: ROLE_CAN_VIEW_NON_PUBLISHED | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/security.xml --> | ||
<config> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
<role id="ROLE_EDITOR">ROLE_CAN_VIEW_NON_PUBLISHED</role> | ||
</config> | ||
|
||
.. code-block:: php | ||
|
||
// app/config/security.php | ||
$container->loadFromExtension('security', array( | ||
'role_hierarchy' => array( | ||
'ROLE_EDITOR' => 'ROLE_CAN_VIEW_NON_PUBLISHED', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unneeded spaces |
||
), | ||
)); | ||
|
||
Once a user with ``ROLE_EDITOR`` is logged in - meaning there is a firewall in place for the path | ||
in question - he will have the permission to view unpublished content as well:: | ||
|
||
use Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishWorkflowChecker; | ||
|
||
// check if current user is allowed to see this document | ||
$publishWorkflowChecker = $container->get('cmf_core.publish_workflow.checker'); | ||
if ($publishWorkflowChecker->isGranted(PublishWorkflowChecker::VIEW_ATTRIBUTE, $document)) { | ||
// ... | ||
} | ||
// check if the document is published, do not make an exception if current | ||
// user would have the right to see this content | ||
if ($publishWorkflowChecker->isGranted(PublishWorkflowChecker::VIEW_ANONYMOUS_ATTRIBUTE, $document)) { | ||
// ... | ||
} | ||
|
||
To check publication in a template, use the twig function ``cmf_is_published``: | ||
|
||
.. code-block:: jinja | ||
|
||
{# check if document is published, regardless of current users role #} | ||
{% if cmf_is_published(page) %} | ||
{# output the document #} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Same on line 155 |
||
{% endif %} | ||
|
||
{# check if current logged in user is allowed to view the document either | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. text should start on a new line |
||
because it is published or because the current user may view unpublished | ||
documents. | ||
#} | ||
{% if is_granted('VIEW', page) %} | ||
{# output the document #} | ||
{% endif %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PHP: <!-- check if document is published, regardless of current users role -->
<?php if ($view['cmf']->isPublished($page)) : ?>
<!-- ... output the document -->
<?php endif ?>
<!--
check if current logged in user is allowed to view the document either
because it is published or because the current user may view unpublished
documents.
-->
<?php if ($view['security']->isGranted('VIEW', $page)) : ?>
<!-- ... output the document -->
<?php endif ?> |
||
|
||
Code that loads content should do the publish checks. Note that the twig | ||
functions already check for publication. Thanks to a | ||
:ref:`request listener <bundle-core-workflow-request_listener>`, routes and | ||
the main content provided by the | ||
:ref:`DynamicRouter <bundles-routing-dynamic_router>` are checked automatically | ||
as well. | ||
|
||
It is possible to set the security token explicitly on the workflow checker. | ||
But by default, the checker will acquire the token from the default security | ||
context, and if there is none (typically when there is no firewall in place for | ||
that URL), an | ||
:class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken` | ||
is created on the fly. | ||
|
||
If you check for ``VIEW`` and not ``VIEW_ANONYMOUS``, the first check is | ||
whether the security context knows the current user and if that user is granted | ||
the bypass role. If so, access is granted, otherwise the decision is delegated to a | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager` | ||
which calls all voters with the requested attributes, the object and the token. | ||
|
||
The decision manager is configured for an unanimous vote with "allow if all | ||
abstain". This means a single voter saying ``ACCESS_DENIED`` is enough for | ||
the content to be considered not published. If all voters abstain (for example | ||
when the content in question does not implement any workflow features) the | ||
content is still considered published. | ||
|
||
Publish Voters | ||
~~~~~~~~~~~~~~ | ||
|
||
A voter has to implement the | ||
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`. | ||
It will get passed a content object and has to decide whether it is published | ||
according to its rules. The CoreBundle provides a couple of generic voters | ||
that check the content for having an interface exposing the methods they need. | ||
If the content implements the interface, they check the parameter and return | ||
``ACCESS_GRANTED`` resp ``ACCESS_DENIED``, otherwise they return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like abbrevations in the docs |
||
``ACCESS_ABSTAIN``. | ||
|
||
As voting is unanimous, each voter returns ``ACCESS_GRANTED`` if its criteria | ||
is met, but if a single voter returns ``ACCESS_DENIED``, the content is | ||
considered not published. | ||
|
||
You can also implement your :ref:`own voters <bundle-core-workflow_custom_voters>` | ||
for additional publication behaviour. | ||
|
||
PublishableVoter | ||
................ | ||
|
||
This voter checks on the ``PublishableInterface`` which simply has a method to | ||
return a boolean value. | ||
|
||
* **isPublishable**: If the object should be considered for publication or not. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lasr items of a list should end with a dot |
||
TimePeriodVoter | ||
............... | ||
|
||
This voter checks on the ``PublishTimePeriodInterface`` which defines a start | ||
and end date. A date may be null to indicate "always started" resp. | ||
"never ending". | ||
|
||
* **getPublishStartDate**: If non-null, the date from which the document | ||
should start being published; | ||
* **getPublishEndDate**: If non-null, the date from which the document | ||
should stop being published. | ||
|
||
.. _bundle-core-workflow_custom_voters: | ||
|
||
Custom Voters | ||
............. | ||
|
||
To build voters with custom logic, you need to implement | ||
:class:`Symfony\\Component\\Security\\Core\\Authentication\\Voter\\VoterInterface` | ||
and define a service with the tag ``cmf_published_voter``. This is similar | ||
to the ``security.voter`` tag, but adds your voter to the publish workflow. As | ||
with the security voters, you can specify a priority, though it is of limited | ||
use as the access decision must be unanimous. If you have more expensive checks, | ||
you can lower the priority of those voters. | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
services: | ||
acme.security.publishable_voter: | ||
class: %my_namespace.security.publishable_voter.class% | ||
tags: | ||
- { name: cmf_published_voter, priority: 30 } | ||
|
||
.. code-block:: xml | ||
|
||
<service id="acme.security.publishable_voter" class="%acme.security.publishable_voter.class%"> | ||
<tag name="cmf_published_voter" priority="30"/> | ||
</service> | ||
|
||
.. code-block:: php | ||
|
||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
$container->register('acme.security.publishable_voter', '%acme.security.publishable_voter.class%') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
->addTag('cmf_published_voter', array('priority' => 30)); | ||
|
||
As the workflow checker will create an | ||
:class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\AnonymousToken` on | ||
the fly if the security context has none, voters must be able to handle this | ||
situation when accessing the user. Also when accessing the security context, | ||
they first must check if it has a token and otherwise not call it to avoid | ||
triggering an exception. If a voter only gives access if there is a current | ||
user fulfills some requirement, it simply has to return ``ACCESS_DENIED`` if | ||
there is no current user. | ||
|
||
.. _bundle-core-workflow-request_listener: | ||
|
||
Publication Request Listener | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The :ref:`DynamicRouter <bundles-routing-dynamic_router>` places the route | ||
object and the main content - if the route has a main content - into the | ||
request attributes. Unless you disable the | ||
``cmf_core.publish_workflow.request_listener``, this listener will listen | ||
on all requests and check publication of both the route object and the main | ||
content object. | ||
|
||
This means that custom templates for ``templates_by_class`` and the controllers | ||
of ``controllers_by_class`` need not check for publication explicitly as its | ||
already done. | ||
|
||
Editing publication information: Publish Workflow Sonata Admin Extension | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
There is a write interface for each publish workflow too, defining setter | ||
methods. The core bundle provides extensions for SonataAdminBundle to easily | ||
add editing of the publish workflow fields to all or selected admins. | ||
|
||
Instead of implementing ``PublishableInterface`` resp. | ||
``PublishTimePeriodInterface`` you models instead need to implement the | ||
``PublishableWriteInterface`` and / or ``PublishTimePeriodWriteInterface``. | ||
|
||
To enable the extensions in your admin classes, simply define the extension | ||
configuration in the ``sonata_admin`` section of your project configuration: | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# app/config/config.yml | ||
sonata_admin: | ||
# ... | ||
extensions: | ||
symfony_cmf_core.publish_workflow.admin_extension.publishable: | ||
implements: | ||
- Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishableWriteInterface | ||
symfony_cmf_core.publish_workflow.admin_extension.time_period: | ||
implements: | ||
- Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishTimePeriodWriteInterface | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/config.xml --> | ||
<config> | ||
<sonata-admin> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these 2 lines should be: <config xmlns="http://sonata-project.org/schema/dic/admin"> |
||
<!-- ... --> | ||
<extensions> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this wrapper element |
||
<extension id="symfony_cmf_core.publish_workflow.admin_extension.publishable"> | ||
<implements> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishableWriteInterface | ||
</implements> | ||
</extension> | ||
<extension id="symfony_cmf_core.publish_workflow.admin_extension.time_period"> | ||
<implements> | ||
Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishTimePeriodWriteInterface | ||
</implements> | ||
</extension> | ||
</extensions> | ||
</sonata-admin> | ||
</config> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wouterj there was some guessing involved here. is this xml config more or less correct? |
||
|
||
.. code-block:: php | ||
|
||
// app/config/config.php | ||
$container->loadFromExtension('sonata_admin', array( | ||
'extensions' => array( | ||
'symfony_cmf_core.admin_extension.publish_workflow' => array( | ||
'implements' => array( | ||
'Symfony\Cmf\Bundle\CoreBundle\PublishWorkflow\PublishWorkflowInterface', | ||
), | ||
), | ||
), | ||
)); | ||
|
||
See the `Sonata Admin extension documentation`_ for more information. | ||
|
||
Dependency Injection Tags | ||
------------------------- | ||
|
@@ -86,15 +377,30 @@ service must have the ``setRequest`` method or you will get a fatal error:: | |
service definition and call ``$this->container->get('request')`` in your | ||
code when you actually need the request. | ||
|
||
cmf_published_voter | ||
~~~~~~~~~~~~~~~~~~~ | ||
|
||
Used to activate :ref:`custom voters <bundle-core-workflow_custom_voters>` for the | ||
:ref:`publish workflow <bundle-core-publish_workflow>` . Tagging a service with | ||
``cmf_published_voter`` integrates it into the access decision of the publish | ||
workflow. | ||
|
||
This tag has the attribute *priority*. The lower the priority number, the | ||
earlier the voter gets to vote. | ||
|
||
Twig extension | ||
-------------- | ||
|
||
Implements the following functions: | ||
This provides a set of useful twig functions for your templates. The functions | ||
respect the :ref:`publish workflow <bundle-core-publish_workflow>` if it is | ||
enabled. | ||
|
||
* **cmf_find**: returns the document for the provided path | ||
* **cmf_find_many**: returns an array of documents for the provided paths | ||
* **cmf_is_published**: checks if the provided document is published | ||
* **cmf_is_published**: checks if the provided document is published, | ||
regardless of an eventual bypass permission of the current user. If you need | ||
the bypass role, you will have a firewall configured for the route the | ||
template is rendered in and can simply use ``{{ is_granted('VIEW', document) }}`` | ||
* **cmf_prev**: returns the previous document by examining the child nodes of | ||
the provided parent | ||
* **cmf_prev_linkable**: returns the previous linkable document by examining | ||
|
@@ -144,3 +450,8 @@ Implements the following functions: | |
{% endif %} | ||
|
||
.. _`CoreBundle`: https://github.com/symfony-cmf/CoreBundle#readme | ||
.. _`Symfony2 security component`: http://www.symfony.com/doc/current/components/security/index.html | ||
.. _`Symfony2 Authorization`: http://www.symfony.com/doc/current/components/security/authorization.html | ||
.. _`Security Chapter`: http://www.symfony.com/doc/current/book/security.html | ||
.. _`ACL checks`: http://www.symfony.com/doc/current/cookbook/security/acl.html | ||
.. _`Sonata Admin extension documentation`: http://sonata-project.org/bundles/admin/master/doc/reference/extensions.html |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be
manager_name
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed it should, but its not. i'll do a separate PR once this change is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rmsint: it would be consistent, yes. but unfortunately its like this in
the actual bundle atm. we should clean this up however. care to do a pr?
or open an issue so we don't forget?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I will do a PR on the CoreBundle to change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use
~
?