1<?php
2
3/**
4 * DokuWiki Plugin saveandedit (Action Component)
5 *
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author  Michael Hamann <michael@content-space.de>
8 */
9
10use dokuwiki\Extension\ActionPlugin;
11use dokuwiki\Extension\EventHandler;
12use dokuwiki\Extension\Event;
13
14class action_plugin_saveandedit extends ActionPlugin
15{
16    /** The action that has been handled before the current action */
17    private $previousAct;
18
19    public function register(EventHandler $controller)
20    {
21       // try to register our handler at a late position so e.g. the edittable plugin has a possibility to process its
22        // data
23        $controller->register_hook(
24            'ACTION_ACT_PREPROCESS',
25            'BEFORE',
26            $this,
27            'handleActionActPreprocess',
28            null,
29            1000
30        );
31        $controller->register_hook('FORM_EDIT_OUTPUT', 'BEFORE', $this, 'handleHtmlEditFormOutput');
32    }
33
34    /**
35     * Clean the environment after saving for the next edit.
36     */
37    private function cleanAfterSave()
38    {
39        global $ID, $INFO, $REV, $RANGE, $TEXT, $PRE, $SUF;
40        $REV = ''; // now we are working on the current revision
41        // Handle section edits
42        if ($PRE || $SUF) {
43            // $from and $to are 1-based indexes of the actually edited content
44            $from = strlen($PRE) + 1;
45            $to = $from + strlen($TEXT);
46            $RANGE = $from . '-' . $to;
47        }
48        // Ensure the current text is loaded again from the file
49        unset($GLOBALS['TEXT'], $GLOBALS['PRE'], $GLOBALS['SUF']);
50        // Reset the date of the last modification to avoid conflict messages
51        unset($GLOBALS['DATE']);
52        // Reset the change check
53        unset($_REQUEST['changecheck']);
54        // Force rendering of the metadata in order to ensure metadata is correct
55        p_set_metadata($ID, [], true);
56        $INFO = pageinfo(); // reset pageinfo to new data (e.g. if the page exists)
57    }
58
59    public function handleActionActPreprocess(Event $event, $param)
60    {
61        global $INPUT;
62
63        if (!$INPUT->bool('saveandedit')) {
64            return;
65        }
66
67        // check if the action was given as array key
68        if (is_array($event->data)) {
69            [$act] = array_keys($event->data);
70        } else {
71            $act = $event->data;
72        }
73
74        // Greebo and above
75        if (class_exists('\\dokuwiki\\ActionRouter', false)) {
76            /*
77               The ACTION_ACT_PREPROCESS event is triggered several
78               times, once for every action. After the save has been
79               executed, the next event is 'draftdel'. We intercept
80               the 'draftdel' action and replace it by 'edit'. As this
81               is a logical place where other plugins may want to save
82               data (e.g. blogtng), we try to be handled relatively
83               late. To fix plugins that want to handle the 'edit'
84               action, we trigger a new event for the 'edit' action.
85            */
86            if ($this->previousAct === 'save' && $act === 'draftdel') {
87                $this->cleanAfterSave();
88                $event->data = 'edit';
89
90                /*
91                   The edittable plugin would restore $TEXT from the
92                   edittable_data post data on each
93                   ACTION_ACT_PREPROCESS call. This breaks the
94                   automatic restore of the prefix and suffix
95                   data. Stop it from doing this by unsetting its
96                   data.
97                */
98                $INPUT->post->remove('edittable_data');
99
100                /*
101                   Stop propagation of the event. All subsequent event
102                   handlers will be called anyway again by the event
103                   triggered below.
104                */
105                $event->stopPropagation();
106
107                /*
108                   Trigger a new event for the edit action.
109                   This ensures that all event handlers for the edit
110                   action are called.  However, we only advise the
111                   before handlers and re-use the default action and
112                   the after handling of the original event.
113                */
114                $new_evt = new Event('ACTION_ACT_PREPROCESS', $event->data);
115                // prevent the default action of the original event
116                if (!$new_evt->advise_before()) {
117                    $event->preventDefault();
118                }
119            }
120            $this->previousAct = $act;
121            // pre-Greebo compatibility
122        } elseif ($act === 'save' && actionOK($act) && act_permcheck($act) == 'save' && checkSecurityToken()) {
123            $event->data = act_save($act);
124            if ($event->data === 'show') {
125                $event->data = 'edit';
126                $this->cleanAfterSave();
127            } elseif ($event->data === 'conflict') {
128                // DokuWiki won't accept 'conflict' as action here.
129                // Just execute save again, the conflict will be detected again
130                $event->data = 'save';
131            }
132        }
133    }
134
135    public function handleHtmlEditFormOutput(Event $event, $param)
136    {
137        global $INPUT;
138
139        $form = $event->data;
140        $pos = $form->findPositionByAttribute('type', 'submit');
141
142        if (!$pos) {
143            // no submit button found, source view
144            return;
145        }
146
147        --$pos;
148
149        $form->addTagOpen('div', $pos++);
150        $attrs = $INPUT->bool('saveandedit') ? ['checked' => 'checked'] : [];
151
152        $cb = $form->addCheckBox('saveandedit', $this->getLang('btn_saveandedit'), $pos++);
153        $cb->attrs = $attrs;
154        $form->addtagClose('div', $pos);
155    }
156}
157
158// vim:ts=4:sw=4:et:
159