1<?php 2 3/** 4 * DokuWiki Plugin struct (Action Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author Andreas Gohr, Michael Große <dokuwiki@cosmocode.de> 8 */ 9 10use dokuwiki\Extension\ActionPlugin; 11use dokuwiki\Extension\EventHandler; 12use dokuwiki\Extension\Event; 13use dokuwiki\plugin\struct\meta\AccessTable; 14use dokuwiki\plugin\struct\meta\AccessTableGlobal; 15use dokuwiki\plugin\struct\meta\AggregationEditorTable; 16use dokuwiki\plugin\struct\meta\Column; 17use dokuwiki\plugin\struct\meta\Schema; 18use dokuwiki\plugin\struct\meta\SearchConfig; 19use dokuwiki\plugin\struct\meta\StructException; 20use dokuwiki\plugin\struct\meta\Value; 21 22/** 23 * Class action_plugin_struct_lookup 24 * 25 * Handle global and serial data table editing 26 */ 27class action_plugin_struct_aggregationeditor extends ActionPlugin 28{ 29 /** @var Column */ 30 protected $column; 31 32 /** @var string */ 33 protected $pid = ''; 34 35 /** @var int */ 36 protected $rid = 0; 37 38 /** 39 * Registers a callback function for a given event 40 * 41 * @param EventHandler $controller DokuWiki's event controller object 42 * @return void 43 */ 44 public function register(EventHandler $controller) 45 { 46 $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'addJsinfo'); 47 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjax'); 48 } 49 50 /** 51 * Add user's permissions to JSINFO 52 * 53 * @param Event $event 54 */ 55 public function addJsinfo(Event $event) 56 { 57 global $ID; 58 global $JSINFO; 59 $JSINFO['plugins']['struct']['isPageEditor'] = auth_quickaclcheck($ID) >= AUTH_EDIT; 60 } 61 62 63 /** 64 * @param Event $event 65 */ 66 public function handleAjax(Event $event) 67 { 68 $len = strlen('plugin_struct_aggregationeditor_'); 69 if (substr($event->data, 0, $len) != 'plugin_struct_aggregationeditor_') { 70 return; 71 } 72 $event->preventDefault(); 73 $event->stopPropagation(); 74 75 try { 76 if (substr($event->data, $len) == 'new') { 77 $this->newRowEditor(); 78 } 79 80 if (substr($event->data, $len) == 'save') { 81 $this->saveRow(); 82 } 83 84 if (substr($event->data, $len) == 'delete') { 85 $this->deleteRow(); 86 } 87 } catch (StructException $e) { 88 http_status(500); 89 header('Content-Type: text/plain'); 90 echo $e->getMessage(); 91 } 92 } 93 94 /** 95 * Deletes a row 96 */ 97 protected function deleteRow() 98 { 99 global $INPUT; 100 $tablename = $INPUT->str('schema'); 101 if (!$tablename) { 102 throw new StructException('No schema given'); 103 } 104 105 $this->rid = $INPUT->int('rid'); 106 $this->validate(); 107 108 action_plugin_struct_inline::checkCSRF(); 109 110 $access = $this->getAccess($tablename); 111 if (!$access->getSchema()->isEditable()) { 112 throw new StructException('lookup delete error: no permission for schema'); 113 } 114 $access->clearData(); 115 } 116 117 /** 118 * Save one new row 119 */ 120 protected function saveRow() 121 { 122 global $INPUT; 123 $tablename = $INPUT->str('schema'); 124 $data = $INPUT->arr('entry'); 125 $this->pid = $INPUT->str('pid'); 126 action_plugin_struct_inline::checkCSRF(); 127 128 // create a new row based on the original aggregation config 129 $access = $this->getAccess($tablename); 130 131 /** @var helper_plugin_struct $helper */ 132 $helper = plugin_load('helper', 'struct'); 133 $helper->saveLookupData($access, $data); 134 135 $config = json_decode($INPUT->str('searchconf'), true, 512, JSON_THROW_ON_ERROR); 136 // update row id 137 $this->rid = $access->getRid(); 138 $config = $this->addTypeFilter($config); 139 140 $editorTable = new AggregationEditorTable( 141 $this->pid, 142 'xhtml', 143 new Doku_Renderer_xhtml(), 144 new SearchConfig($config) 145 ); 146 147 echo $editorTable->getFirstRow(); 148 } 149 150 /** 151 * Create the Editor for a new row 152 */ 153 protected function newRowEditor() 154 { 155 global $INPUT; 156 global $lang; 157 158 $searchconf = $INPUT->arr('searchconf'); 159 $tablename = $searchconf['schemas'][0][0]; 160 161 $schema = new Schema($tablename); 162 if (!$schema->isEditable()) { 163 return; 164 } // no permissions, no editor 165 // separate check for serial data in JS 166 167 echo '<div class="struct_entry_form">'; 168 echo '<fieldset>'; 169 echo '<legend>' . $this->getLang('lookup new entry') . '</legend>'; 170 /** @var action_plugin_struct_edit $edit */ 171 $edit = plugin_load('action', 'struct_edit'); 172 173 // filter columns based on searchconf cols from syntax 174 $columns = $this->resolveColumns($searchconf, $schema); 175 176 foreach ($columns as $column) { 177 $label = $column->getLabel(); 178 $field = new Value($column, ''); 179 echo $edit->makeField($field, "entry[$label]"); 180 } 181 formSecurityToken(); // csrf protection 182 echo '<input type="hidden" name="call" value="plugin_struct_aggregationeditor_save" />'; 183 echo '<input type="hidden" name="schema" value="' . hsc($tablename) . '" />'; 184 185 echo '<button type="submit">' . $lang['btn_save'] . '</button>'; 186 187 echo '<div class="err"></div>'; 188 echo '</fieldset>'; 189 echo '</div>'; 190 } 191 192 /** 193 * Names of columns in the new entry editor: either all, 194 * or the selection defined in config. If config contains '*', 195 * just return the full set. 196 * 197 * @param array $searchconf 198 * @param Schema $schema 199 * @return array 200 */ 201 protected function resolveColumns($searchconf, $schema) 202 { 203 // if no valid column config, return all columns 204 if ( 205 empty($searchconf['cols']) || 206 !is_array($searchconf['cols']) || 207 in_array('*', $searchconf['cols']) 208 ) { 209 return $schema->getColumns(false); 210 } 211 212 $columns = []; 213 foreach ($searchconf['cols'] as $col) { 214 $columns[] = $schema->findColumn($col); 215 } 216 // filter invalid columns (where findColumn() returned false) 217 return array_filter($columns); 218 } 219 220 /** 221 * Returns data accessor 222 * 223 * @param string $tablename 224 * @return AccessTableGlobal 225 */ 226 protected function getAccess($tablename) 227 { 228 if ($this->pid) { 229 return AccessTable::getSerialAccess($tablename, $this->pid, $this->rid); 230 } 231 return AccessTable::getGlobalAccess($tablename, $this->rid); 232 } 233 234 /** 235 * Adds filter to search config to differentiate data types 236 * 237 * @param array $config 238 * @return array 239 */ 240 protected function addTypeFilter($config) 241 { 242 $config['filter'][] = ['%rowid%', '=', $this->rid, 'AND']; 243 if ($this->pid) { 244 $config['filter'][] = ['%pageid%', '=', $this->pid, 'AND']; 245 } 246 return $config; 247 } 248 249 /** 250 * Throws an exception if data is invalid 251 */ 252 protected function validate() 253 { 254 if (!$this->rid) { 255 throw new StructException('No row id given'); 256 } 257 } 258} 259