xref: /plugin/struct/action/inline.php (revision 858c5caacc413c761478da4a6902f549ead9e841)
14731b875SAndreas Gohr<?php
24731b875SAndreas Gohr/**
34731b875SAndreas Gohr * DokuWiki Plugin struct (Action Component)
44731b875SAndreas Gohr *
54731b875SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
64731b875SAndreas Gohr * @author  Andreas Gohr, Michael Große <dokuwiki@cosmocode.de>
74731b875SAndreas Gohr */
84731b875SAndreas Gohr
94731b875SAndreas Gohr// must be run within Dokuwiki
104ec54c67SAndreas Gohruse dokuwiki\plugin\struct\meta\AccessTable;
1194c9aa4cSAndreas Gohruse dokuwiki\plugin\struct\meta\AccessTableData;
1293ca6f4fSAndreas Gohruse dokuwiki\plugin\struct\meta\Column;
134731b875SAndreas Gohruse dokuwiki\plugin\struct\meta\StructException;
1493ca6f4fSAndreas Gohruse dokuwiki\plugin\struct\meta\ValueValidator;
154731b875SAndreas Gohr
164731b875SAndreas Gohrif(!defined('DOKU_INC')) die();
174731b875SAndreas Gohr
184731b875SAndreas Gohr/**
194731b875SAndreas Gohr * Class action_plugin_struct_inline
204731b875SAndreas Gohr *
214731b875SAndreas Gohr * Handle inline editing
224731b875SAndreas Gohr */
234731b875SAndreas Gohrclass action_plugin_struct_inline extends DokuWiki_Action_Plugin {
244731b875SAndreas Gohr
2594c9aa4cSAndreas Gohr    /** @var  AccessTableData */
264731b875SAndreas Gohr    protected $schemadata = null;
274731b875SAndreas Gohr
284731b875SAndreas Gohr    /** @var  Column */
294731b875SAndreas Gohr    protected $column = null;
304731b875SAndreas Gohr
314731b875SAndreas Gohr    /** @var String */
324731b875SAndreas Gohr    protected $pid = '';
334731b875SAndreas Gohr
344731b875SAndreas Gohr    /**
354731b875SAndreas Gohr     * Registers a callback function for a given event
364731b875SAndreas Gohr     *
374731b875SAndreas Gohr     * @param Doku_Event_Handler $controller DokuWiki's event controller object
384731b875SAndreas Gohr     * @return void
394731b875SAndreas Gohr     */
404731b875SAndreas Gohr    public function register(Doku_Event_Handler $controller) {
414731b875SAndreas Gohr        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax');
424731b875SAndreas Gohr    }
434731b875SAndreas Gohr
444731b875SAndreas Gohr    /**
454731b875SAndreas Gohr     * @param Doku_Event $event
464731b875SAndreas Gohr     * @param $param
474731b875SAndreas Gohr     */
484731b875SAndreas Gohr    public function handle_ajax(Doku_Event $event, $param) {
494731b875SAndreas Gohr        $len = strlen('plugin_struct_inline_');
504731b875SAndreas Gohr        if(substr($event->data, 0, $len) != 'plugin_struct_inline_') return;
514731b875SAndreas Gohr        $event->preventDefault();
524731b875SAndreas Gohr        $event->stopPropagation();
534731b875SAndreas Gohr
544731b875SAndreas Gohr        if(substr($event->data, $len) == 'editor') {
554731b875SAndreas Gohr            $this->inline_editor();
564731b875SAndreas Gohr        }
574731b875SAndreas Gohr
584731b875SAndreas Gohr        if(substr($event->data, $len) == 'save') {
594731b875SAndreas Gohr            try {
604731b875SAndreas Gohr                $this->inline_save();
614731b875SAndreas Gohr            } catch(StructException $e) {
624731b875SAndreas Gohr                http_status(500);
634731b875SAndreas Gohr                header('Content-Type: text/plain; charset=utf-8');
644731b875SAndreas Gohr                echo $e->getMessage();
654731b875SAndreas Gohr            }
664731b875SAndreas Gohr        }
67cdd09a96SAndreas Gohr
68cdd09a96SAndreas Gohr        if(substr($event->data, $len) == 'cancel') {
69cdd09a96SAndreas Gohr            $this->inline_cancel();
70cdd09a96SAndreas Gohr        }
714731b875SAndreas Gohr    }
724731b875SAndreas Gohr
73cdd09a96SAndreas Gohr    /**
74cdd09a96SAndreas Gohr     * Creates the inline editor
75cdd09a96SAndreas Gohr     */
764731b875SAndreas Gohr    protected function inline_editor() {
77cdd09a96SAndreas Gohr        // silently fail when editing not possible
784731b875SAndreas Gohr        if(!$this->initFromInput()) return;
79cdd09a96SAndreas Gohr        if(auth_quickaclcheck($this->pid) < AUTH_EDIT) return;
806ebbbb8eSAndreas Gohr        if(!$this->schemadata->getSchema()->isEditable()) return;
81cdd09a96SAndreas Gohr        if(checklock($this->pid)) return;
824731b875SAndreas Gohr
83cdd09a96SAndreas Gohr        // lock page
84cdd09a96SAndreas Gohr        lock($this->pid);
854731b875SAndreas Gohr
86cdd09a96SAndreas Gohr        // output the editor
874731b875SAndreas Gohr        $value = $this->schemadata->getDataColumn($this->column);
8863e019d0SAndreas Gohr        echo '<label data-column="' . hsc($this->column->getFullQualifiedLabel()) . '">';
894731b875SAndreas Gohr        echo $value->getValueEditor('entry');
9063e019d0SAndreas Gohr        echo '</label>';
914731b875SAndreas Gohr        $hint = $this->column->getType()->getTranslatedHint();
924731b875SAndreas Gohr        if($hint) {
934731b875SAndreas Gohr            echo '<div class="hint">';
944731b875SAndreas Gohr            echo hsc($hint);
954731b875SAndreas Gohr            echo '</div>';
964731b875SAndreas Gohr        }
97cdd09a96SAndreas Gohr
98cdd09a96SAndreas Gohr        // csrf protection
99cdd09a96SAndreas Gohr        formSecurityToken();
1004731b875SAndreas Gohr    }
1014731b875SAndreas Gohr
102cdd09a96SAndreas Gohr    /**
103cdd09a96SAndreas Gohr     * Save the data posted by the inline editor
104cdd09a96SAndreas Gohr     */
1054731b875SAndreas Gohr    protected function inline_save() {
1064731b875SAndreas Gohr        global $INPUT;
1074731b875SAndreas Gohr
10813eddb0fSAndreas Gohr        // check preconditions
1094d2da382SAndreas Gohr        if(!$this->initFromInput()) {
1104d2da382SAndreas Gohr            throw new StructException('inline save error: init');
1114d2da382SAndreas Gohr        }
11213eddb0fSAndreas Gohr        self::checkCSRF();
11313eddb0fSAndreas Gohr        if(!$this->schemadata->getSchema()->isLookup()) {
11413eddb0fSAndreas Gohr            $this->checkPage();
1154731b875SAndreas Gohr        }
1166ebbbb8eSAndreas Gohr        if(!$this->schemadata->getSchema()->isEditable()) {
1176ebbbb8eSAndreas Gohr            throw new StructException('inline save error: no permission for schema');
1186ebbbb8eSAndreas Gohr        }
1194731b875SAndreas Gohr
1204731b875SAndreas Gohr        // validate
1214731b875SAndreas Gohr        $value = $INPUT->param('entry');
12293ca6f4fSAndreas Gohr        $validator = new ValueValidator();
1234731b875SAndreas Gohr        if(!$validator->validateValue($this->column, $value)) {
1244731b875SAndreas Gohr            throw new StructException(join("\n", $validator->getErrors()));
1254731b875SAndreas Gohr        }
1264731b875SAndreas Gohr
1274731b875SAndreas Gohr        // current data
1284731b875SAndreas Gohr        $tosave = $this->schemadata->getDataArray();
1294731b875SAndreas Gohr        $tosave[$this->column->getLabel()] = $value;
1304731b875SAndreas Gohr
1314731b875SAndreas Gohr        // save
13213eddb0fSAndreas Gohr        if($this->schemadata->getSchema()->isLookup()) {
13313eddb0fSAndreas Gohr            $revision = 0;
13413eddb0fSAndreas Gohr        } else {
13513eddb0fSAndreas Gohr            $revision = helper_plugin_struct::createPageRevision($this->pid, 'inline edit');
136*858c5caaSMichael Grosse            p_get_metadata($this->pid); // reparse the metadata of the page top update the titles/rev/lasteditor table
13713eddb0fSAndreas Gohr        }
13813eddb0fSAndreas Gohr        $this->schemadata->setTimestamp($revision);
13913eddb0fSAndreas Gohr        try {
14013eddb0fSAndreas Gohr            if(!$this->schemadata->saveData($tosave)) {
14113eddb0fSAndreas Gohr                throw new StructException('saving failed');
14213eddb0fSAndreas Gohr            }
14313eddb0fSAndreas Gohr        } finally {
14413eddb0fSAndreas Gohr            // unlock (unlocking a non-existing file is okay,
14513eddb0fSAndreas Gohr            // so we don't check if it's a lookup here
146cdd09a96SAndreas Gohr            unlock($this->pid);
14713eddb0fSAndreas Gohr        }
1484731b875SAndreas Gohr
1494731b875SAndreas Gohr        // reinit then render
1504731b875SAndreas Gohr        $this->initFromInput();
1514731b875SAndreas Gohr        $value = $this->schemadata->getDataColumn($this->column);
1524731b875SAndreas Gohr        $R = new Doku_Renderer_xhtml();
1534731b875SAndreas Gohr        $value->render($R, 'xhtml'); // FIXME use configured default renderer
1544731b875SAndreas Gohr        echo $R->doc;
1554731b875SAndreas Gohr    }
1564731b875SAndreas Gohr
1574731b875SAndreas Gohr    /**
158cdd09a96SAndreas Gohr     * Unlock a page (on cancel action)
159cdd09a96SAndreas Gohr     */
160cdd09a96SAndreas Gohr    protected function inline_cancel() {
161cdd09a96SAndreas Gohr        global $INPUT;
162cdd09a96SAndreas Gohr        $pid = $INPUT->str('pid');
163cdd09a96SAndreas Gohr        unlock($pid);
164cdd09a96SAndreas Gohr    }
165cdd09a96SAndreas Gohr
166cdd09a96SAndreas Gohr    /**
1674731b875SAndreas Gohr     * Initialize internal state based on input variables
1684731b875SAndreas Gohr     *
1694731b875SAndreas Gohr     * @return bool if initialization was successfull
1704731b875SAndreas Gohr     */
1714731b875SAndreas Gohr    protected function initFromInput() {
1724731b875SAndreas Gohr        global $INPUT;
1734731b875SAndreas Gohr
1744731b875SAndreas Gohr        $this->schemadata = null;
1754731b875SAndreas Gohr        $this->column = null;
1764731b875SAndreas Gohr
1774731b875SAndreas Gohr        $pid = $INPUT->str('pid');
1784731b875SAndreas Gohr        list($table, $field) = explode('.', $INPUT->str('field'));
1794731b875SAndreas Gohr        if(blank($pid)) return false;
1804731b875SAndreas Gohr        if(blank($table)) return false;
1814731b875SAndreas Gohr        if(blank($field)) return false;
1824731b875SAndreas Gohr
1834731b875SAndreas Gohr        $this->pid = $pid;
1844ec54c67SAndreas Gohr        try {
1854ec54c67SAndreas Gohr            $this->schemadata = AccessTable::byTableName($table, $pid);
1864ec54c67SAndreas Gohr        } catch(StructException $ignore) {
1874731b875SAndreas Gohr            return false;
1884731b875SAndreas Gohr        }
1894731b875SAndreas Gohr
1904ec54c67SAndreas Gohr        $this->column = $this->schemadata->getSchema()->findColumn($field);
1914731b875SAndreas Gohr        if(!$this->column || !$this->column->isVisibleInEditor()) {
1924731b875SAndreas Gohr            $this->schemadata = null;
1934731b875SAndreas Gohr            $this->column = null;
1944731b875SAndreas Gohr            return false;
1954731b875SAndreas Gohr        }
1964731b875SAndreas Gohr
1974731b875SAndreas Gohr        return true;
1984731b875SAndreas Gohr    }
1994731b875SAndreas Gohr
20013eddb0fSAndreas Gohr    /**
20113eddb0fSAndreas Gohr     * Checks if a page can be edited
20213eddb0fSAndreas Gohr     *
20313eddb0fSAndreas Gohr     * @throws StructException when check fails
20413eddb0fSAndreas Gohr     */
20513eddb0fSAndreas Gohr    protected function checkPage() {
20613eddb0fSAndreas Gohr        if(!page_exists($this->pid)) {
20713eddb0fSAndreas Gohr            throw new StructException('inline save error: no such page');
20813eddb0fSAndreas Gohr        }
20913eddb0fSAndreas Gohr        if(auth_quickaclcheck($this->pid) < AUTH_EDIT) {
21013eddb0fSAndreas Gohr            throw new StructException('inline save error: acl');
21113eddb0fSAndreas Gohr        }
21213eddb0fSAndreas Gohr        if(checklock($this->pid)) {
21313eddb0fSAndreas Gohr            throw new StructException('inline save error: lock');
21413eddb0fSAndreas Gohr        }
21513eddb0fSAndreas Gohr    }
21613eddb0fSAndreas Gohr
21713eddb0fSAndreas Gohr    /**
21813eddb0fSAndreas Gohr     * Our own implementation of checkSecurityToken because we don't want the msg() call
21913eddb0fSAndreas Gohr     *
22013eddb0fSAndreas Gohr     * @throws StructException when check fails
22113eddb0fSAndreas Gohr     */
222c498205aSAndreas Gohr    public static function checkCSRF() {
22313eddb0fSAndreas Gohr        global $INPUT;
22413eddb0fSAndreas Gohr        if(
22513eddb0fSAndreas Gohr            $INPUT->server->str('REMOTE_USER') &&
22613eddb0fSAndreas Gohr            getSecurityToken() != $INPUT->str('sectok')
22713eddb0fSAndreas Gohr        ) {
22813eddb0fSAndreas Gohr            throw new StructException('CSRF check failed');
22913eddb0fSAndreas Gohr        }
23013eddb0fSAndreas Gohr    }
23113eddb0fSAndreas Gohr
2324731b875SAndreas Gohr}
233