-
Notifications
You must be signed in to change notification settings - Fork 2
Add publish_needs_approve
option to enforce approval before publishing
#27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
2ef4cda
d00b394
e82ec62
b06ec25
b0a2e57
c67fe74
5bfdaed
6e775b9
32c60c0
cebb655
52bb9c0
88db78f
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 |
---|---|---|
|
@@ -9,13 +9,19 @@ | |
*/ | ||
class action_plugin_structpublish_banner extends DokuWiki_Action_Plugin | ||
{ | ||
/** @var \helper_plugin_structpublish_db */ | ||
/** | ||
* @var \helper_plugin_structpublish_db | ||
*/ | ||
protected $dbHelper; | ||
|
||
/** @var bool */ | ||
/** | ||
* @var bool | ||
*/ | ||
protected $compactView; | ||
|
||
/** @inheritDoc */ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register(Doku_Event_Handler $controller) | ||
{ | ||
$controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'renderBanner'); | ||
|
@@ -68,11 +74,10 @@ public function renderBanner(Doku_Event $event) | |
$banner .= $this->getBannerText('previous_publish', $prevpubRevision, $shownRevision->getRev()); | ||
} | ||
|
||
// link to newest draft, if exists, is not shown already and user has a role | ||
if ( | ||
$newestRevision->getRev() != $shownRevision->getRev() && | ||
$newestRevision->getStatus() != Constants::STATUS_PUBLISHED && | ||
$this->dbHelper->checkAccess($ID) | ||
// link to newest draft, if exists, if not shown already, if user has ACL write | ||
if ($newestRevision->getRev() != $shownRevision->getRev() | ||
&& $newestRevision->getStatus() != Constants::STATUS_PUBLISHED | ||
&& auth_quickaclcheck($ID) >= 2 | ||
) { | ||
$banner .= $this->getBannerText('latest_draft', $newestRevision, $shownRevision->getRev()); | ||
} | ||
|
@@ -92,8 +97,8 @@ public function renderBanner(Doku_Event $event) | |
/** | ||
* Fills place holder texts with data from the given Revision | ||
* | ||
* @param string $name | ||
* @param Revision $rev | ||
* @param string $name | ||
* @param Revision $rev | ||
* @return string | ||
*/ | ||
protected function getBannerText($name, $rev, $diff = '') | ||
|
@@ -128,9 +133,9 @@ protected function getBannerText($name, $rev, $diff = '') | |
/** | ||
* Create a HTML link to a specific revision | ||
* | ||
* @param string $id page id | ||
* @param int $rev revision to link to | ||
* @param int $text the link text to use | ||
* @param string $id page id | ||
* @param int $rev revision to link to | ||
* @param int $text the link text to use | ||
* @return string | ||
*/ | ||
protected function makeLink($id, $rev, $text) | ||
|
@@ -142,44 +147,61 @@ protected function makeLink($id, $rev, $text) | |
/** | ||
* Create the form for approval and publishing | ||
* | ||
* @param string $status current status | ||
* @param string $newVersion suggested new Version | ||
* @param string $status current status | ||
* @param string $newVersion suggested new Version | ||
* @return string | ||
*/ | ||
protected function actionButtons($status, $newVersion) | ||
{ | ||
|
||
global $ID; | ||
|
||
// If the status is published, return an empty string | ||
if ($status === Constants::STATUS_PUBLISHED) { | ||
return ''; | ||
} | ||
|
||
// Create a new form instance | ||
$form = new dokuwiki\Form\Form(); | ||
|
||
if ( | ||
$status !== Constants::STATUS_APPROVED && | ||
$this->dbHelper->checkAccess($ID, [Constants::ACTION_APPROVE]) | ||
|
||
if ($status !== Constants::STATUS_APPROVED | ||
&& $this->dbHelper->checkAccess($ID, [Constants::ACTION_APPROVE]) | ||
) { | ||
$form->addButton( | ||
'structpublish[' . Constants::ACTION_APPROVE . ']', | ||
$this->getLang('action_' . Constants::ACTION_APPROVE) | ||
)->attr('type', 'submit'); | ||
$form->addButton( | ||
'structpublish[' . Constants::ACTION_APPROVE . ']', | ||
$this->getLang('action_' . Constants::ACTION_APPROVE) | ||
)->attr('type', 'submit'); | ||
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. note to self: this part seems to be the same, just reformatted |
||
} | ||
|
||
if ($this->dbHelper->checkAccess($ID, [Constants::ACTION_PUBLISH])) { | ||
$form->addTextInput('version', $this->getLang('newversion'))->val($newVersion); | ||
$form->addButton( | ||
'structpublish[' . Constants::ACTION_PUBLISH . ']', | ||
$this->getLang('action_' . Constants::ACTION_PUBLISH) | ||
)->attr('type', 'submit'); | ||
} | ||
// Add the publish button only if the status is approved and the user has access | ||
if ((bool)$this->getConf('publish_needs_approve')) { | ||
if ($status === Constants::STATUS_APPROVED && $this->dbHelper->checkAccess($ID, [Constants::ACTION_PUBLISH])) { | ||
$form->addTextInput('version', $this->getLang('newversion'))->val($newVersion); | ||
$form->addButton( | ||
'structpublish[' . Constants::ACTION_PUBLISH . ']', | ||
$this->getLang('action_' . Constants::ACTION_PUBLISH) | ||
)->attr('type', 'submit'); | ||
} | ||
} else { | ||
if ($this->dbHelper->checkAccess($ID, [Constants::ACTION_PUBLISH])) { | ||
$form->addTextInput('version', $this->getLang('newversion'))->val($newVersion); | ||
$form->addButton( | ||
'structpublish[' . Constants::ACTION_PUBLISH . ']', | ||
$this->getLang('action_' . Constants::ACTION_PUBLISH) | ||
)->attr('type', 'submit'); | ||
} | ||
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 button output is the same. Makes no sense to repeat it. The code should decide if the button is to be shown or not (based on config, access and status) and then just do it. 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. ok will change that |
||
|
||
} | ||
// Return the HTML representation of the form | ||
return $form->toHTML(); | ||
} | ||
|
||
|
||
/** | ||
* Tries to increase a given version | ||
* | ||
* @param string $version | ||
* @param string $version | ||
* @return string | ||
*/ | ||
protected function increaseVersion($version) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?php | ||
|
||
use dokuwiki\plugin\structpublish\meta\Constants; | ||
use dokuwiki\plugin\structpublish\meta\Revision; | ||
|
||
/** | ||
* Action component providing (localized) dw2pdf replacements | ||
* | ||
* @STATUS@ draft / approved / published | ||
* @APPROVER@ user that approved the draft | ||
* @APPROVALDATE@ date of approval | ||
* @PUBLISHER@ user that published the page | ||
* @PUBLISHDATE@ date of publishing | ||
* @VERSION@ shown version | ||
* @REVISION@ shown revision | ||
* @LATESTPUBLISHEDVERSION@ latest published version | ||
* @LATESTPUBLISHEDREVISION@ latest published revision | ||
* | ||
* @author Josquin Dehaene <jo@foobarjo.org> | ||
* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html | ||
*/ | ||
|
||
class action_plugin_structpublish_dw2pdf extends DokuWiki_Action_Plugin | ||
{ | ||
/** | ||
* @var \helper_plugin_structpublish_db | ||
*/ | ||
protected $dbHelper; | ||
|
||
public function register(Doku_Event_Handler $controller) | ||
{ | ||
$controller->register_hook('PLUGIN_DW2PDF_REPLACE', 'BEFORE', $this, 'provide_structpublish_replacements'); | ||
$controller->register_hook('PLUGIN_DW2PDF_REPLACE', 'AFTER', $this, 'clean_structpublish_replacements'); | ||
} | ||
|
||
/** | ||
* Provide StructPublish values for DW2PDF replacements | ||
*/ | ||
public function provide_structpublish_replacements(Doku_Event $event) | ||
{ | ||
global $ID; | ||
global $INFO; | ||
global $REV; | ||
|
||
//force reload of the globals. usefull when coming from bookcreator | ||
$keep = $ID; | ||
$ID = $event->data['id']; | ||
$INFO = pageinfo(); | ||
$REV = $event->data['rev']; | ||
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. Why are you changing the globals here? An action handler for the PDF Plugin should not change globals. If that change is somehow necessary for rendering within a bookcreator process, that should probably be fixed in book creator or if that's not possible, at a proper place in dw2pdf, struct or structpublish (depending on who is at fault). But it should not be a side effect of handling PDF generation. 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. When coming from BookCreator, $ID refers to the BookCreator page, so we cannot retrieve the metadata of the page currently being rendered. This is why we need to manually initialize the variables. |
||
|
||
$this->dbHelper = plugin_load('helper', 'structpublish_db'); | ||
|
||
if (!$this->dbHelper->isPublishable()) { | ||
return; | ||
} | ||
|
||
// get revisions | ||
$newestRevision = new Revision($ID, $INFO['currentrev']); | ||
if ($REV) { | ||
$shownRevision = new Revision($ID, $REV); | ||
} else { | ||
$shownRevision = $newestRevision; | ||
} | ||
$latestpubRevision = $newestRevision->getLatestPublishedRevision(); | ||
$prevpubRevision = $shownRevision->getLatestPublishedRevision($REV ?: $INFO['currentrev']); | ||
$prevapprovedRevision = $shownRevision->getLatestApprovedRevision($REV ?: $INFO['currentrev']); | ||
|
||
// get redactor | ||
$pageMeta = p_get_metadata($ID); | ||
$event->data['replace']['@REDACTOR@'] = $pageMeta['last_change']['user']; | ||
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. redactor is the one who approved the the change? I am not opposed to the term but I don't think we use it anywhere else. Might make sense to streamline the naming. 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. Perhaps the variable name could be improved—for example, $EDITOR for clarity—as it refers to the last editor of the page. approver and publisher are potentially different users 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. yes, then "editor" is the better name. |
||
|
||
// get lastest published version & revision | ||
if ($latestpubRevision != null) { | ||
$event->data['replace']['@LATESTPUBLISHEDVERSION@'] = $latestpubRevision->getVersion(); | ||
$event->data['replace']['@LATESTPUBLISHEDREVISION@'] = $latestpubRevision->getRev(); | ||
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 would add a couple of underscores for better readability, but that's nitpicking. 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. ok |
||
}else{ | ||
$event->data['replace']['@LATESTPUBLISHEDVERSION@'] = $this->getLang("status_na"); | ||
$event->data['replace']['@LATESTPUBLISHEDREVISION@'] = $this->getLang("status_na");; | ||
} | ||
|
||
// get status and revision | ||
$event->data['replace']['@STATUS@'] = $this->getLang("status_" . $shownRevision->getStatus()); | ||
$event->data['replace']['@REVISION@'] = $shownRevision->getRev(); | ||
|
||
// status draft | ||
if ($event->data['replace']['@STATUS@'] === $this->getLang("status_draft")) { | ||
$event->data['replace']['@VERSION@'] = $this->getLang("status_draft"); | ||
$event->data['replace']['@APPROVER@'] = $this->getLang("status_na"); | ||
$event->data['replace']['@APPROVALDATE@'] = $this->getLang("status_na"); | ||
$event->data['replace']['@PUBLISHER@'] = $this->getLang("status_na"); | ||
$event->data['replace']['@PUBLISHDATE@'] = $this->getLang("status_na"); | ||
} | ||
|
||
// status approved | ||
if ($event->data['replace']['@STATUS@'] === $this->getLang("status_approved")) { | ||
$event->data['replace']['@VERSION@'] = $this->getLang("status_approved"); | ||
$event->data['replace']['@APPROVER@'] = $shownRevision->getUser(); | ||
$event->data['replace']['@APPROVALDATE@'] = $shownRevision->getDateTime(); | ||
$event->data['replace']['@PUBLISHER@'] = $this->getLang("status_na"); | ||
$event->data['replace']['@PUBLISHDATE@'] = $this->getLang("status_na"); | ||
} | ||
|
||
// status published | ||
if ($event->data['replace']['@STATUS@'] === $this->getLang("status_published")) { | ||
$event->data['replace']['@VERSION@'] = $shownRevision->getVersion(); | ||
$event->data['replace']['@APPROVER@'] = $prevapprovedRevision->getUser(); | ||
$event->data['replace']['@APPROVALDATE@'] = $prevapprovedRevision->getDateTime(); | ||
$event->data['replace']['@PUBLISHER@'] = $shownRevision->getUser(); | ||
$event->data['replace']['@PUBLISHDATE@'] = $shownRevision->getDateTime(); | ||
} | ||
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 would also prefix all variables with 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. ok |
||
$ID = $keep; | ||
} | ||
|
||
/** | ||
* Clean up replacements in DW2PDF content | ||
*/ | ||
public function clean_structpublish_replacements(Doku_Event $event) | ||
{ | ||
$event->data['content'] = str_replace( | ||
['@APPROVER@', '@APPROVALDATE@', '@LATESTPUBLISHEDREVISION@', '@REVISION@', '@PUBLISHER@', '@PUBLISHDATE@', '@VERSION@', '@STATUS@', '@REDACTOR@' , '@LATESTPUBLISHEDVERSION@'], | ||
['', '', '', '', '', '', '', '', '', ''], | ||
$event->data['content'] | ||
); | ||
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 understand this handler. Why is is it necessary? Shouldn't the BEFORE handler already handle all of those? 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 clears the StructPublish variables to ensure they are not accidentally reused where they shouldn’t be. 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. No, it runs a second replace operation on the page content. It does not clear any variables. What exactly is the issue you're trying to fix here? 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 might have miss something, but I was thinking this could clean up the replacements since it’s called on an AFTER hook: $controller->register_hook('PLUGIN_DW2PDF_REPLACE', 'AFTER', $this, 'clean_structpublish_replacements'); |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,9 @@ | |
*/ | ||
class action_plugin_structpublish_save extends DokuWiki_Action_Plugin | ||
{ | ||
/** @inheritDoc */ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register(Doku_Event_Handler $controller) | ||
{ | ||
$controller->register_hook('COMMON_WIKIPAGE_SAVE', 'AFTER', $this, 'handleSave'); | ||
|
@@ -19,12 +21,14 @@ public function register(Doku_Event_Handler $controller) | |
/** | ||
* Handle the page save event to store revision meta data | ||
* | ||
* @param Doku_Event $event | ||
* @param Doku_Event $event | ||
* @return void | ||
*/ | ||
public function handleSave(Doku_Event $event) | ||
{ | ||
/** @var helper_plugin_structpublish_db $dbHelper */ | ||
/** | ||
* @var helper_plugin_structpublish_db $dbHelper | ||
*/ | ||
$dbHelper = plugin_load('helper', 'structpublish_db'); | ||
|
||
$id = $event->data['id']; | ||
|
@@ -38,6 +42,8 @@ public function handleSave(Doku_Event $event) | |
|
||
$revision = new Revision($id, $event->data['newRevision']); | ||
$revision->setStatus(Constants::STATUS_DRAFT); | ||
$revision->setUser($_SERVER['REMOTE_USER']); | ||
$revision->setTimestamp(time()); | ||
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. What's this about? It seems to be unrelated to this PR. Is it fixing a bug? Under which circumstances? I feel like this should be a separate PR? 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. It simply updates the metadata stored in the StructPublish database. Previously, the user and timestamp were available for approved and published revisions, but not for drafts, which remained anonymous. |
||
|
||
try { | ||
$revision->save(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,14 @@ | |
|
||
class action_plugin_structpublish_show extends DokuWiki_Action_Plugin | ||
{ | ||
/** @var int */ | ||
/** | ||
* @var int | ||
*/ | ||
protected static $latestPublishedRev; | ||
|
||
/** @inheritDoc */ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function register(Doku_Event_Handler $controller) | ||
{ | ||
$controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handleShow'); | ||
|
@@ -18,7 +22,7 @@ public function register(Doku_Event_Handler $controller) | |
/** | ||
* Decide which revision to show based on role assignments | ||
* | ||
* @param Doku_Event $event | ||
* @param Doku_Event $event | ||
* @return void | ||
*/ | ||
public function handleShow(Doku_Event $event) | ||
|
@@ -31,20 +35,22 @@ public function handleShow(Doku_Event $event) | |
global $REV; | ||
global $INFO; | ||
|
||
/** @var helper_plugin_structpublish_db $dbHelper */ | ||
/** | ||
* @var helper_plugin_structpublish_db $dbHelper | ||
*/ | ||
$dbHelper = plugin_load('helper', 'structpublish_db'); | ||
|
||
if ( | ||
!$dbHelper->isPublishable() || | ||
(auth_isadmin() && !$this->getConf('restrict_admin')) | ||
if (!$dbHelper->isPublishable() | ||
|| (auth_isadmin() && !$this->getConf('restrict_admin')) | ||
) { | ||
return; | ||
} | ||
|
||
$currentRevision = new Revision($ID, $REV ?: $INFO['currentrev']); | ||
$isPublished = $currentRevision->getStatus() === Constants::STATUS_PUBLISHED; | ||
|
||
if (!$dbHelper->isPublisher($ID)) { | ||
//Show published version or nothing to user with no Role and no ACL write | ||
if (!$dbHelper->isPublisher($ID) && auth_quickaclcheck($ID) < 2) { | ||
$latestPublished = $currentRevision->getLatestPublishedRevision(); | ||
// there is no published revision, show nothing | ||
if (!$isPublished && is_null($latestPublished)) { | ||
|
@@ -67,17 +73,18 @@ public function handleShow(Doku_Event $event) | |
* Suppress message about viewing an old revision if it is the latest one | ||
* that the current user is allowed to see. | ||
* | ||
* @param Doku_Event $event | ||
* @param Doku_Event $event | ||
* @return void | ||
*/ | ||
public function handleShowrev(Doku_Event $event) | ||
{ | ||
/** @var helper_plugin_structpublish_db $dbHelper */ | ||
/** | ||
* @var helper_plugin_structpublish_db $dbHelper | ||
*/ | ||
$dbHelper = plugin_load('helper', 'structpublish_db'); | ||
|
||
if ( | ||
!$dbHelper->isPublishable() || | ||
(auth_isadmin() && !$this->getConf('restrict_admin')) | ||
if (!$dbHelper->isPublishable() | ||
|| (auth_isadmin() && !$this->getConf('restrict_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. note to self: there seem to be no changes here. just formatting. 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. Actually, I made a change on line 47. This is where we instruct StructPublish to show only the published revision of a page (or nothing) to the user, based on the ACL rather than their role. (I’ve already change the 2 for AUTH_EDIT) 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. |
||
) { | ||
return; | ||
} | ||
|
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.
This should use the AUTH_EDIT constant, not a number.
I assume this change is what you mentioned here:
@moka-dev Can you explain the scenario you're addressing here?
@annda can you have a look at this change from
$this->dbHelper->checkAccess()
toauth_quickaclcheck()
and chime in with your thoughts?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.
Previously, a user without a publish role but with write ACL could create a new page in a StructPublish-controlled section. However, once the page was saved, the user could no longer access it, which I assume is not the intended behavior. Any user with write ACL on a section should be able to create and edit pages within it—especially the ones they created—considering that approvers and publishers may be different users.
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.
That explanation does make sense to me and I vaguely remember discussion between me and @annda about this but not the outcome. @annda I think we should discuss this and then write down what we decide on the wiki page. I think this mechanism needs to be thought through and then be implemented, tested and documented.
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.
Right, we have been aware of this problem for some time, see #5 and #15
I will post more in the relevant issues, since this PR will be closed anyway and split it into smaller ones.