1<?php 2 3/** 4 * DokuWiki Plugin acknowledge (AJAX Action Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author Andreas Gohr, Anna Dabrowska <dokuwiki@cosmocode.de> 8 */ 9 10use dokuwiki\Extension\ActionPlugin; 11use dokuwiki\Extension\EventHandler; 12use dokuwiki\Extension\Event; 13use dokuwiki\Form\Form; 14 15class action_plugin_acknowledge_ajax extends ActionPlugin 16{ 17 /** @inheritDoc */ 18 public function register(EventHandler $controller) 19 { 20 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxAcknowledge'); 21 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxAutocomplete'); 22 $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxUserList'); 23 } 24 25 /** 26 * @param Event $event 27 * @param $param 28 */ 29 public function handleAjaxAcknowledge(Event $event, $param) 30 { 31 if ($event->data === 'plugin_acknowledge_acknowledge') { 32 $event->stopPropagation(); 33 $event->preventDefault(); 34 35 global $INPUT; 36 $id = $INPUT->str('id'); 37 38 if (page_exists($id)) { 39 echo $this->html(); 40 } 41 } 42 } 43 44 /** 45 * @param Event $event 46 * @return void 47 */ 48 public function handleAjaxAutocomplete(Event $event) 49 { 50 if ($event->data === 'plugin_acknowledge_autocomplete') { 51 if (!checkSecurityToken()) return; 52 53 global $INPUT; 54 55 $event->stopPropagation(); 56 $event->preventDefault(); 57 58 /** @var helper_plugin_acknowledge $hlp */ 59 $hlp = $this->loadHelper('acknowledge'); 60 61 $found = []; 62 63 if ($INPUT->has('user')) { 64 $search = $INPUT->str('user'); 65 $knownUsers = $hlp->getUsers(); 66 $found = array_filter($knownUsers, function ($user) use ($search) { 67 return (strstr(strtolower($user['label']), strtolower($search))) !== false ? $user : null; 68 }); 69 } 70 71 if ($INPUT->has('pg')) { 72 $search = $INPUT->str('pg'); 73 $pages = ft_pageLookup($search, true); 74 $found = array_map(function ($id, $title) { 75 return ['value' => $id, 'label' => $title ?? $id]; 76 }, array_keys($pages), array_values($pages)); 77 } 78 79 header('Content-Type: application/json'); 80 81 echo json_encode($found); 82 } 83 } 84 85 /** 86 * Returns the full user list for a report section (loaded on demand) 87 * 88 * @param Event $event 89 * @return void 90 */ 91 public function handleAjaxUserList(Event $event) 92 { 93 if ($event->data !== 'plugin_acknowledge_userlist') return; 94 95 $event->stopPropagation(); 96 $event->preventDefault(); 97 98 if (!auth_ismanager()) return; 99 100 global $INPUT; 101 $id = $INPUT->str('id'); 102 $status = $INPUT->str('status'); 103 104 if (!page_exists($id)) return; 105 if (!in_array($status, ['current', 'due'], true)) return; 106 107 /** @var helper_plugin_acknowledge $helper */ 108 $helper = plugin_load('helper', 'acknowledge'); 109 110 if (!$helper->getPageAssignees($id)) return; 111 112 echo $this->userListHtml($helper->getPageAcknowledgements($id, '', $status)); 113 } 114 115 /** 116 * Returns the acknowledgment form/confirmation and optionally management report 117 * 118 * @return string The HTML to display 119 */ 120 protected function html() 121 { 122 global $INPUT; 123 $id = $INPUT->str('id'); 124 $user = $INPUT->server->str('REMOTE_USER'); 125 if ($id === '' || $user === '') return ''; 126 127 /** @var helper_plugin_acknowledge $helper */ 128 $helper = plugin_load('helper', 'acknowledge'); 129 130 return $this->bannerHtml($id, $user, $helper) . $this->reportHtml($id, $helper); 131 } 132 133 /** 134 * Returns the personal acknowledgement banner 135 * 136 * @param string $id 137 * @param string $user 138 * @param helper_plugin_acknowledge $helper 139 * @return string 140 */ 141 protected function bannerHtml($id, $user, helper_plugin_acknowledge $helper) 142 { 143 global $INPUT; 144 global $USERINFO; 145 146 // only display for users assigned to the page 147 if (!$helper->isUserAssigned($id, $user, $USERINFO['grps'])) { 148 return ''; 149 } 150 151 // if the approve plugin is active, only show if the page is approved 152 if ($helper->isBlockedByApprove($id)) { 153 return ''; 154 } 155 156 if ($INPUT->bool('ack')) { 157 $helper->saveAcknowledgement($id, $user); 158 } 159 160 $ack = $helper->hasUserAcknowledged($id, $user); 161 162 $html = '<div class="plugin-acknowledge-box ack' . ($ack ? ' done' : '') . '">'; 163 $html .= '<div class="ack-icon">'; 164 $html .= inlineSVG(__DIR__ . '/../admin.svg'); 165 $html .= '</div>'; 166 167 $html .= '<div class="content">'; 168 if ($ack) { 169 $html .= '<h4>'; 170 $html .= $this->getLang('ackOk'); 171 $html .= '</h4>'; 172 $html .= sprintf($this->getLang('ackGranted'), dformat($ack)); 173 } else { 174 $html .= '<h4>' . $this->getLang('ackRequired') . '</h4>'; 175 $latest = $helper->getLatestUserAcknowledgement($id, $user); 176 if ($latest) { 177 $html .= '<a href="' 178 . wl($id, ['do' => 'diff', 'at' => $latest], false, '&') . '">' 179 . sprintf($this->getLang('ackDiff'), dformat($latest)) 180 . '</a><br>'; 181 } 182 183 $form = new Form(['id' => 'ackForm']); 184 $form->addCheckbox('ack', $this->getLang('ackText'))->attr('required', 'required'); 185 $form->addHTML( 186 '<br><button type="submit" name="acksubmit" id="ack-submit">' 187 . $this->getLang('ackButton') 188 . '</button>' 189 ); 190 191 $html .= $form->toHTML(); 192 } 193 $html .= '</div>'; // content 194 $html .= '</div>'; // box 195 196 return $html; 197 } 198 199 /** 200 * Returns the manager/admin report box 201 * 202 * @param string $id 203 * @param helper_plugin_acknowledge $helper 204 * @return string 205 */ 206 protected function reportHtml($id, helper_plugin_acknowledge $helper) 207 { 208 $mode = $this->getConf('onpage_report'); 209 if ($mode === 'off') return ''; 210 211 if (!auth_ismanager()) return ''; 212 213 if (!$helper->getPageAssignees($id)) return ''; 214 215 $html = '<div class="plugin-acknowledge-box report">'; 216 217 $html .= '<div class="ack-icon">'; 218 $html .= inlineSVG(__DIR__ . '/../admin.svg'); 219 $html .= '</div>'; 220 221 $html .= '<div class="content">'; 222 $html .= '<h3>' . $this->getLang('reportTitle') . '</h3>'; 223 224 // resolve group membership once, derive both counts arithmetically 225 $counts = $helper->getPageAcknowledgementCounts($id); 226 227 if ($mode === 'acknowledged' || $mode === 'both') { 228 $html .= '<p>' . $this->getLang('reportAcknowledgedTitle') . '</p>'; 229 $html .= $this->userCountHtml($id, 'current', $counts['current']); 230 } 231 232 if ($mode === 'pending' || $mode === 'both') { 233 $html .= '<p>' . $this->getLang('reportPendingTitle') . '</p>'; 234 $html .= $this->userCountHtml($id, 'due', $counts['due']); 235 } 236 237 $html .= '</div>'; // content 238 $html .= '</div>'; // box 239 240 return $html; 241 } 242 243 /** 244 * Renders a clickable user count that loads the full user list on demand 245 * 246 * @param string $id 247 * @param string $status acknowledgement status ('current' or 'due') 248 * @param int $count 249 * @return string 250 */ 251 protected function userCountHtml($id, $status, $count) 252 { 253 if (!$count) { 254 return '<p>' . $this->getLang('reportNobody') . '</p>'; 255 } 256 257 return '<p><a href="#" class="plugin-acknowledge-loadusers"' 258 . ' data-id="' . hsc($id) . '" data-status="' . hsc($status) . '">' 259 . sprintf($this->getLang('reportUserCount'), $count) 260 . '</a></p>'; 261 } 262 263 /** 264 * Renders a list of users from acknowledgement records. 265 * 266 * @param array $rows 267 * @return string 268 */ 269 protected function userListHtml($rows) 270 { 271 if (!$rows) { 272 return '<p>' . $this->getLang('reportNobody') . '</p>'; 273 } 274 275 $html = '<ul>'; 276 foreach ($rows as $row) { 277 $html .= '<li>'; 278 $html .= userlink($row['user']); 279 280 if (!empty($row['ack'])) { 281 $html .= ' ' . $this->getLang('reportAckedOn') . ' ' . hsc(dformat($row['ack'])); 282 } 283 $html .= '</li>'; 284 } 285 $html .= '</ul>'; 286 287 return $html; 288 } 289} 290