Issues (109)

action/editor.php (3 issues)

1
<?php
2
3
/**
4
 * DokuWiki Plugin prosemirror (Action Component)
5
 *
6
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7
 * @author  Andreas Gohr <[email protected]>
8
 */
9
10
use dokuwiki\Extension\ActionPlugin;
11
use dokuwiki\Extension\EventHandler;
12
use dokuwiki\Extension\Event;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Event. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use dokuwiki\Form\Form;
14
use dokuwiki\Form\ButtonElement;
15
16
class action_plugin_prosemirror_editor extends ActionPlugin
17
{
18
    /**
19
     * Registers a callback function for a given event
20
     *
21
     * @param EventHandler $controller DokuWiki's event controller object
22
     *
23
     * @return void
24
     */
25
    public function register(EventHandler $controller)
26
    {
27
        $controller->register_hook('ACTION_HEADERS_SEND', 'BEFORE', $this, 'forceWYSIWYG');
28
        $controller->register_hook('ACTION_HEADERS_SEND', 'AFTER', $this, 'addJSINFO');
29
        $controller->register_hook('FORM_EDIT_OUTPUT', 'BEFORE', $this, 'addDataAndToggleButton');
30
        $controller->register_hook('TPL_ACT_RENDER', 'AFTER', $this, 'addAddtionalForms');
31
    }
32
33
    /**
34
     * If the current user is forced to use the WYSIWYG editor, set the cookie accordingly
35
     *
36
     * Triggered by event: ACTION_HEADERS_SEND
37
     *
38
     * @param Event $event
39
     * @param            $param
40
     */
41
    public function forceWYSIWYG(Event $event, $param)
42
    {
43
        if ($this->isForceWYSIWYG()) {
44
            set_doku_pref('plugin_prosemirror_useWYSIWYG', true);
45
        }
46
    }
47
48
    /**
49
     * Add the editor toggle button and, if using the WYSIWYG editor, the instructions rendered to json
50
     *
51
     * Triggered by event: HTML_EDITFORM_OUTPUT
52
     *
53
     * @param Event $event event object
54
     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
55
     *                           handler was registered]
56
     *
57
     * @return void
58
     */
59
    public function addDataAndToggleButton(Event $event, $param)
60
    {
61
        if (!$this->allowWYSIWYG()) {
62
            return;
63
        }
64
65
        /** @var Doku_Form|Form $form */
66
        $form = $event->data;
67
68
        // return early if content is not editable
69
        if ($this->isReadOnly($form)) return;
70
71
72
        $useWYSIWYG = get_doku_pref('plugin_prosemirror_useWYSIWYG', false);
73
74
        $prosemirrorJSON = '';
75
        if ($useWYSIWYG) {
76
            global $TEXT;
77
            $instructions = p_get_instructions($TEXT);
78
            try {
79
                $prosemirrorJSON = p_render('prosemirror', $instructions, $info);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $info seems to be never defined.
Loading history...
80
            } catch (Throwable $e) {
81
                $errorMsg = 'Rendering the page\'s syntax for the WYSIWYG editor failed: ' . $e->getMessage();
82
83
                /** @var \helper_plugin_prosemirror $helper */
84
                $helper = plugin_load('helper', 'prosemirror');
85
                if ($helper->tryToLogErrorToSentry($e, ['text' => $TEXT])) {
86
                    $errorMsg .= ' -- The error has been logged to Sentry.';
87
                }
88
89
                msg($errorMsg, -1);
90
                return;
91
            }
92
        }
93
94
        $form->addElement($this->buildToggleButton(), 0);
95
        $form->setHiddenField('prosemirror_json', $prosemirrorJSON);
96
        $form->addHTML('<div class="prosemirror_wrapper" id="prosemirror__editor"></div>', 1);
97
    }
98
99
    /**
100
     * Create the button to toggle the WYSIWYG editor
101
     *
102
     * Creates it as hidden if forcing WYSIWYG
103
     *
104
     * @return ButtonElement
105
     */
106
    protected function buildToggleButton()
107
    {
108
        $button = new ButtonElement('prosemirror', $this->getLang('switch_editors'));
109
        $button->attr('type', 'button');
110
        $button->addClass('button plugin_prosemirror_useWYSIWYG');
111
        if ($this->isForceWYSIWYG()) {
112
            $button->attr('style', 'display: none;');
113
        }
114
        return $button;
115
    }
116
117
    /**
118
     * Determine if the current user is forced to use the WYSIWYG editor
119
     *
120
     * @return bool
121
     */
122
    protected function isForceWYSIWYG()
123
    {
124
        return $this->getConf('forceWYSIWYG') && !auth_ismanager();
125
    }
126
127
    /**
128
     * Forbid using WYSIWYG editor when editing anything else then sections or the entire page
129
     *
130
     * This would be the case for the edittable editor or the editor of the data plugin
131
     *
132
     * @return bool
133
     */
134
    protected function allowWYSIWYG()
135
    {
136
        global $INPUT;
137
        return !$INPUT->has('target') || $INPUT->str('target') === 'section';
138
    }
139
140
    public function addAddtionalForms(Event $event)
141
    {
142
        if (!$this->allowWYSIWYG()) {
143
            return;
144
        }
145
146
        if (!in_array($event->data, ['edit', 'preview'])) {
147
            return;
148
        }
149
150
        $linkForm = new Form([
151
            'class' => 'plugin_prosemirror_linkform',
152
            'id' => 'prosemirror-linkform',
153
            'style' => 'display: none;',
154
        ]);
155
        $linkForm->addFieldsetOpen('Links')->addClass('js-link-fieldset');
156
        $iwOptions = array_keys(getInterwiki());
157
        $linkForm->addDropdown('iwshortcut', $iwOptions, 'InterWiki')->attr('required', 'required');
158
159
        $linkForm->addButtonHTML('linkwiz', inlineSVG(DOKU_PLUGIN . 'prosemirror/images/link.svg'))->attrs([
0 ignored issues
show
The constant DOKU_PLUGIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
160
            'type' => 'button',
161
            'class' => 'js-open-linkwiz linkform_linkwiz'
162
        ]);
163
        $linkForm->addTextInput('linktarget', $this->getLang('link target'))->attrs(
164
            [
165
            'required' => 'required',
166
            'autofocus' => 'autofocus',
167
            ]
168
        );
169
170
        $linkForm->addTagOpen('div')->addClass('radio-wrapper');
171
        $linkForm->addTagOpen('fieldset');
172
        $linkForm->addTagOpen('legend');
173
        $linkForm->addHTML('Link Type');
174
        $linkForm->addTagClose('legend');
175
        $linkForm->addRadioButton('linktype', $this->getLang('type:wiki page'))->val('internallink');
176
        $linkForm->addRadioButton('linktype', $this->getLang('type:interwiki'))->val('interwikilink');
177
        $linkForm->addRadioButton('linktype', $this->getLang('type:email'))->val('emaillink');
178
        $linkForm->addRadioButton('linktype', $this->getLang('type:external'))
179
            ->val('externallink')
180
            ->attr('checked', 'checked');
181
        $linkForm->addRadioButton('linktype', $this->getLang('type:other'))->val('other');
182
        $linkForm->addTagClose('fieldset');
183
        $linkForm->addTagClose('div');
184
185
        $linkForm->addTagOpen('div')->addClass('radio-wrapper');
186
        $linkForm->addTagOpen('fieldset');
187
        $linkForm->addTagOpen('legend');
188
        $linkForm->addHTML('Link Name Type');
189
        $linkForm->addTagClose('legend');
190
        $linkForm->addRadioButton('nametype', $this->getLang('type:automatic title'))
191
            ->val('automatic')
192
            ->attr('checked', 'checked');
193
        $linkForm->addRadioButton('nametype', $this->getLang('type:custom title'))->val('custom');
194
        $linkForm->addRadioButton('nametype', $this->getLang('type:image'))->val('image');
195
        $linkForm->addTextInput('linkname', 'Link name')->attr('placeholder', $this->getLang('placeholder:link name'));
196
        $linkForm->addTagOpen('div')->addClass('js-media-wrapper');
197
        $linkForm->addTagClose('div');
198
        $linkForm->addTagClose('fieldset');
199
        $linkForm->addTagClose('div');
200
201
202
        $linkForm->addFieldsetClose();
203
        $linkForm->addButton('ok-button', 'OK')->attr('type', 'submit');
204
        $linkForm->addButton('cancel-button', $this->getLang('cancel'))->attr('type', 'button');
205
206
        echo $linkForm->toHTML();
207
208
        $mediaForm = new Form([
209
            'class' => 'plugin_prosemirror_mediaform',
210
            'id' => 'prosemirror-mediaform',
211
            'style' => 'display: none;',
212
        ]);
213
        $mediaForm->addFieldsetOpen($this->getLang('legend:media'))->addClass('js-media-fieldset');
214
        $mediaForm->addButtonHTML(
215
            'mediamanager',
216
            inlineSVG(DOKU_PLUGIN . 'prosemirror/images/file-image-outline.svg')
217
        )->attrs([
218
                'type' => 'button',
219
                'class' => 'js-open-mediamanager mediaform_mediamanager'
220
        ]);
221
        $mediaForm->addTextInput('mediatarget', $this->getLang('media target'))->attrs(
222
            [
223
                'required' => 'required',
224
                'autofocus' => 'autofocus',
225
            ]
226
        );
227
        $mediaForm->addTextInput('mediacaption', $this->getLang('label:caption'));
228
229
        $mediaForm->addTagOpen('div')->addClass('image-properties');
230
        $mediaForm->addTagOpen('p');
231
        $mediaForm->addHTML($this->getLang('label:image_properties'));
232
        $mediaForm->addTagClose('p');
233
234
        $mediaForm->addTagOpen('div')->addClass('input-wrapper');
235
        $mediaForm->addTagOpen('fieldset');
236
        $mediaForm->addTagOpen('legend');
237
        $mediaForm->addHTML($this->getLang('legend:size'));
238
        $mediaForm->addTagClose('legend');
239
        $mediaForm->addTextInput('width', $this->getLang('label:width'))->attr('type', 'number');
240
        $mediaForm->addTextInput('height', $this->getLang('label:height'))->attr('type', 'number');
241
        $mediaForm->addTagClose('fieldset');
242
        $mediaForm->addTagClose('div');
243
244
        $mediaForm->addTagOpen('div')->addClass('input-wrapper');
245
        $mediaForm->addTagOpen('fieldset');
246
        $mediaForm->addTagOpen('legend');
247
        $mediaForm->addHTML($this->getLang('legend:alignment'));
248
        $mediaForm->addTagClose('legend');
249
        $mediaForm->addRadioButton('alignment', $this->getLang('label:default alignment'))
250
            ->val('')
251
            ->attr('checked', 'checked');
252
        $mediaForm->addRadioButton('alignment', $this->getLang('label:float left'))->val('left');
253
        $mediaForm->addRadioButton('alignment', $this->getLang('label:center alignment'))->val('center');
254
        $mediaForm->addRadioButton('alignment', $this->getLang('label:float right'))->val('right');
255
        $mediaForm->addTagClose('fieldset');
256
        $mediaForm->addTagClose('div');
257
258
        $mediaForm->addTagOpen('div')->addClass('input-wrapper');
259
        $mediaForm->addTagOpen('fieldset');
260
        $mediaForm->addTagOpen('legend');
261
        $mediaForm->addHTML($this->getLang('legend:linking'));
262
        $mediaForm->addTagClose('legend');
263
        $mediaForm->addRadioButton('linking', $this->getLang('label:default linking'))
264
            ->val('details')
265
            ->attr('checked', 'checked');
266
        $mediaForm->addRadioButton('linking', $this->getLang('label:direct linking'))->val('direct');
267
        $mediaForm->addRadioButton('linking', $this->getLang('label:nolink'))->val('nolink');
268
        $mediaForm->addRadioButton('linking', $this->getLang('label:linkonly'))->val('linkonly');
269
        $mediaForm->addTagClose('fieldset');
270
        $mediaForm->addTagClose('div');
271
272
        $mediaForm->addTagOpen('div')->addClass('input-wrapper');
273
        $mediaForm->addTagOpen('fieldset');
274
        $mediaForm->addTagOpen('legend');
275
        $mediaForm->addHTML($this->getLang('legend:caching'));
276
        $mediaForm->addTagClose('legend');
277
        $mediaForm->addRadioButton('caching', $this->getLang('label:default caching'))
278
            ->val('')
279
            ->attr('checked', 'checked');
280
        $mediaForm->addRadioButton('caching', $this->getLang('label:recache'))->val('recache');
281
        $mediaForm->addRadioButton('caching', $this->getLang('label:nocache'))->val('nocache');
282
        $mediaForm->addTagClose('fieldset');
283
        $mediaForm->addTagClose('div');
284
285
        $mediaForm->addTagClose('div'); // end of image-properties
286
287
        $mediaForm->addFieldsetClose();
288
        $mediaForm->addButton('ok-button', 'OK')->attr('type', 'submit');
289
        $mediaForm->addButton('cancel-button', $this->getLang('cancel'))->attr('type', 'button');
290
291
        // dynamic image hack? https://www.dokuwiki.org/images#dynamic_images
292
293
        echo $mediaForm->toHTML();
294
295
        // phpcs:disable
296
        $languages = explode(' ', '4cs 6502acme 6502kickass 6502tasm 68000devpac abap actionscript3 actionscript ada aimms algol68 apache applescript apt_sources arm asm asp asymptote autoconf autohotkey autoit avisynth awk bascomavr bash basic4gl batch bf biblatex bibtex blitzbasic bnf boo caddcl cadlisp ceylon cfdg cfm chaiscript chapel cil c_loadrunner clojure c_mac cmake cobol coffeescript c cpp cpp-qt cpp-winapi csharp css cuesheet c_winapi dart dcl dcpu16 dcs delphi diff div dos dot d ecmascript eiffel email epc e erlang euphoria ezt f1 falcon fo fortran freebasic freeswitch fsharp gambas gdb genero genie gettext glsl gml gnuplot go groovy gwbasic haskell haxe hicest hq9plus html html4strict html5 icon idl ini inno intercal io ispfpanel java5 java javascript jcl j jquery julia kixtart klonec klonecpp kotlin latex lb ldif lisp llvm locobasic logtalk lolcode lotusformulas lotusscript lscript lsl2 lua m68k magiksf make mapbasic mathematica matlab mercury metapost mirc mk-61 mmix modula2 modula3 mpasm mxml mysql nagios netrexx newlisp nginx nimrod nsis oberon2 objc objeck ocaml-brief ocaml octave oobas oorexx oracle11 oracle8 oxygene oz parasail parigp pascal pcre perl6 perl per pf phix php-brief php pic16 pike pixelbender pli plsql postgresql postscript povray powerbuilder powershell proftpd progress prolog properties providex purebasic pycon pys60 python qbasic qml q racket rails rbs rebol reg rexx robots rpmspec rsplus ruby rust sas sass scala scheme scilab scl sdlbasic smalltalk smarty spark sparql sql standardml stonescript swift systemverilog tclegg tcl teraterm texgraph text thinbasic tsql twig typoscript unicon upc urbi uscript vala vbnet vb vbscript vedit verilog vhdl vim visualfoxpro visualprolog whitespace whois winbatch xbasic xml xojo xorg_conf xpp yaml z80 zxbasic');
297
        // phpcs:enable
298
        $datalistHTML = '<datalist id="codelanguages">';
299
        foreach ($languages as $language) {
300
            $datalistHTML .= "<option value=\"$language\">";
301
        }
302
        $datalistHTML .= '</datalist>';
303
        echo $datalistHTML;
304
    }
305
306
    /**
307
     * Provide the current smiley configuration to Javascript
308
     */
309
    public function addJSINFO()
310
    {
311
        global $JSINFO;
312
        $JSINFO['SMILEY_CONF'] = getSmileys();
313
    }
314
315
    /**
316
     * Returns true if the current content is read only
317
     *
318
     * @todo remove Doku_Form case when the class is removed
319
     *
320
     * @param $form
321
     * @return bool
322
     */
323
    protected function isReadOnly($form)
324
    {
325
        if (is_a($form, Form::class)) {
326
            $textareaPos = $form->findPositionByType('textarea');
327
            $readonly = $textareaPos !== false && !empty($form->getElementAt($textareaPos)->attr('readonly'));
328
        } else {
329
            /** @var Doku_Form $form */
330
            $textareaPos = $form->findElementByType('wikitext');
331
            $readonly = $textareaPos !== false && !empty($form->getElementAt($textareaPos)['readonly']);
332
        }
333
        return $readonly;
334
    }
335
}
336
337
// vim:ts=4:sw=4:et:
338