1<?php 2 3// must be run within Dokuwiki 4if (!defined('DOKU_INC')) die(); 5class helper_plugin_approve extends DokuWiki_Plugin { 6 7 /** 8 * @param helper_plugin_sqlite $sqlite 9 * @return string 10 */ 11 public function no_apr_namespace(helper_plugin_sqlite $sqlite) { 12 //check for config update 13 $key = 'no_apr_namespaces'; 14 $res = $sqlite->query('SELECT value FROM config WHERE key=?', $key); 15 $no_apr_namespaces_db = $sqlite->res2single($res); 16 $no_apr_namespaces_conf = $this->getConf($key); 17 //update internal config 18 if ($no_apr_namespaces_db != $no_apr_namespaces_conf) { 19 $sqlite->query('UPDATE config SET value=? WHERE key=?', $no_apr_namespaces_conf, $key); 20 21 $res = $sqlite->query('SELECT page, hidden FROM page'); 22 $pages = $sqlite->res2arr($res); 23 foreach ($pages as $page) { 24 $id = $page['page']; 25 $hidden = $page['hidden']; 26 $in_hidden_namespace = $this->in_hidden_namespace($sqlite, $id, $no_apr_namespaces_conf); 27 $new_hidden = $in_hidden_namespace ? '1' : '0'; 28 29 if ($hidden != $new_hidden) { 30 $sqlite->query('UPDATE page SET hidden=? WHERE page=?', $new_hidden, $id); 31 } 32 } 33 } 34 35 return $no_apr_namespaces_conf; 36 } 37 38 /** 39 * @param helper_plugin_sqlite $sqlite 40 * @param $id 41 * @param null $approver 42 * @return bool 43 */ 44 public function use_approve_here(helper_plugin_sqlite $sqlite, $id, &$approver=null) { 45 46 //check if we should update no_apr_namespace 47 $this->no_apr_namespace($sqlite); 48 49 $res = $sqlite->query('SELECT page, approver FROM page WHERE page=? AND hidden=0', $id); 50 $row = $sqlite->res2row($res); 51 if ($row) { 52 $approver = $row['approver']; 53 return true; 54 } 55 return false; 56 } 57 58 /** 59 * @param helper_plugin_sqlite $sqlite 60 * @param $id 61 * @return bool|string 62 */ 63 public function find_last_approved(helper_plugin_sqlite $sqlite, $id) { 64 $res = $sqlite->query('SELECT rev FROM revision 65 WHERE page=? AND approved IS NOT NULL 66 ORDER BY rev DESC LIMIT 1', $id); 67 return $sqlite->res2single($res); 68 } 69 70 /** 71 * @param helper_plugin_sqlite $sqlite 72 * @param null $no_apr_namespaces 73 * @return array|array[]|false|string[] 74 */ 75 public function get_hidden_namespaces_list(helper_plugin_sqlite $sqlite, $no_apr_namespaces=null) { 76 if (!$no_apr_namespaces) { 77 $no_apr_namespaces = $this->no_apr_namespace($sqlite); 78 } 79 80 $no_apr_namespaces_list = preg_split('/\s+/', $no_apr_namespaces,-1, 81 PREG_SPLIT_NO_EMPTY); 82 $no_apr_namespaces_list = array_map(function ($namespace) { 83 return ltrim($namespace, ':'); 84 }, $no_apr_namespaces_list); 85 86 return $no_apr_namespaces_list; 87 } 88 89 /** 90 * @param helper_plugin_sqlite $sqlite 91 * @param $id 92 * @param null $no_apr_namespaces 93 * @return bool|string 94 */ 95 public function in_hidden_namespace(helper_plugin_sqlite $sqlite, $id, $no_apr_namespaces=null) { 96 $no_apr_namespaces_list = $this->get_hidden_namespaces_list($sqlite, $no_apr_namespaces); 97 $id = ltrim($id, ':'); 98 foreach ($no_apr_namespaces_list as $namespace) { 99 if (substr($id, 0, strlen($namespace)) == $namespace) { 100 return true; 101 } 102 } 103 return false; 104 } 105 106 /** 107 * @param helper_plugin_sqlite $sqlite 108 * @return array 109 */ 110 public function weighted_assignments(helper_plugin_sqlite $sqlite) { 111 $res = $sqlite->query('SELECT id,namespace,approver FROM maintainer'); 112 $assignments = $sqlite->res2arr($res); 113 114 $weighted_assignments = []; 115 foreach ($assignments as $assignment) { 116 $ns = $assignment['namespace']; 117 //more general namespaces are overridden by more specific ones. 118 if (substr($ns, -1) == '*') { 119 $weight = substr_count($ns, ':'); 120 } else { 121 $weight = PHP_INT_MAX; 122 } 123 124 $assignment['weight'] = $weight; 125 $weighted_assignments[] = $assignment; 126 } 127 array_multisort(array_column($weighted_assignments, 'weight'), $weighted_assignments); 128 129 return $weighted_assignments; 130 } 131 132 /** 133 * @param helper_plugin_sqlite $sqlite 134 * @param $id 135 * @param null $pageApprover 136 * @param null $weighted_assignments 137 * @return bool 138 */ 139 public function isPageAssigned(helper_plugin_sqlite $sqlite, $id, &$pageApprover=null, $weighted_assignments=null) { 140 if (!$weighted_assignments) { 141 $weighted_assignments = $this->weighted_assignments($sqlite); 142 } 143 foreach ($weighted_assignments as $assignment) { 144 $ns = ltrim($assignment['namespace'], ':'); 145 $approver = $assignment['approver']; 146 if (substr($ns, -2) == '**') { 147 //remove '**' 148 $ns = substr($ns, 0, -2); 149 if (substr($id, 0, strlen($ns)) == $ns) { 150 $newAssignment = true; 151 $pageApprover = $approver; 152 } 153 } elseif (substr($ns, -1) == '*') { 154 //remove '*' 155 $ns = substr($ns, 0, -1); 156 $noNS = substr($id, strlen($ns)); 157 if (strpos($noNS, ':') === FALSE && 158 substr($id, 0, strlen($ns)) == $ns) { 159 $newAssignment = true; 160 $pageApprover = $approver; 161 } 162 } elseif($id == $ns) { 163 $newAssignment = true; 164 $pageApprover = $approver; 165 } 166 } 167 return $newAssignment; 168 } 169 170 /** 171 * @param helper_plugin_sqlite $sqlite 172 */ 173 public function updatePagesAssignments(helper_plugin_sqlite $sqlite) 174 { 175 //clean current settings 176 $sqlite->query('DELETE FROM page'); 177 178 $wikiPages = $this->getPages(); 179 $no_apr_namespace = $this->no_apr_namespace($sqlite); 180 $weighted_assignments = $this->weighted_assignments($sqlite); 181 foreach ($wikiPages as $id) { 182 if ($this->isPageAssigned($sqlite, $id, $approver, $weighted_assignments)) { 183 $data = [ 184 'page' => $id, 185 'hidden' => $this->in_hidden_namespace($sqlite, $id, $no_apr_namespace) ? '1' : '0' 186 ]; 187 if (!blank($approver)) { 188 $data['approver'] = $approver; 189 } 190 $sqlite->storeEntry('page', $data); 191 } 192 } 193 } 194 195 /** 196 * @param string $approver 197 * @return bool 198 */ 199 public function isGroup($approver) { 200 if (!$approver) return false; 201 if (strncmp($approver, "@", 1) === 0) return true; 202 return false; 203 } 204 205 /** 206 * @param $userinfo 207 * @param string $group 208 * @return bool 209 */ 210 public function isInGroup($userinfo, $group) { 211 $groupname = substr($group, 1); 212 if (in_array($groupname, $userinfo['grps'])) return true; 213 return false; 214 } 215 216 /** 217 * @param $id 218 * @param string $pageApprover 219 * @return bool 220 */ 221 public function client_can_approve($id, $pageApprover) { 222 global $INFO; 223 //user not log in 224 if (!isset($INFO['userinfo'])) return false; 225 226 if ($pageApprover == $INFO['client']) { 227 return true; 228 } elseif ($this->isGroup($pageApprover) && $this->isInGroup($INFO['userinfo'], $pageApprover)) { 229 return true; 230 //no approver provided, check if approve plugin apply here 231 } elseif (auth_quickaclcheck($id) >= AUTH_DELETE && 232 (!$pageApprover || !$this->getConf('strict_approver'))) { 233 return true; 234 } 235 236 return false; 237 } 238 239 /** 240 * @param $id 241 * @return bool 242 */ 243 public function client_can_mark_ready_for_approval($id) { 244 global $INFO; 245 246 $ready_for_approval_acl = preg_split('/\s+/', $this->getConf('ready_for_approval_acl'), -1, PREG_SPLIT_NO_EMPTY); 247 if (count($ready_for_approval_acl) == 0) return auth_quickaclcheck($id) >= AUTH_EDIT; // empty 248 foreach ($ready_for_approval_acl as $user_or_group) { 249 if ($user_or_group[0] == '@' && $this->isInGroup($INFO['userinfo'], $user_or_group)) { 250 return true; 251 } elseif ($user_or_group == $INFO['client']) { 252 return true; 253 } 254 } 255 return false; 256 } 257 258 /** 259 * @param $id 260 * @return bool 261 */ 262 public function client_can_see_drafts($id, $pageApprover) { 263 // in view mode no one can see drafts 264 if ($this->getConf('viewmode') && get_doku_pref('approve_viewmode', false)) return false; 265 266 if (!$this->getConf('hide_drafts_for_viewers')) return true; 267 268 if (auth_quickaclcheck($id) >= AUTH_EDIT) return true; 269 if ($this->client_can_approve($id, $pageApprover)) return true; 270 271 return false; 272 } 273 274 /** 275 * Get the array of all pages ids in wiki 276 * 277 * @return array 278 */ 279 public function getPages() { 280 global $conf; 281 282 $datadir = realpath($conf['datadir']); // path without ending "/" 283 $directory = new RecursiveDirectoryIterator($datadir, FilesystemIterator::SKIP_DOTS); 284 $iterator = new RecursiveIteratorIterator($directory); 285 286 $pages = []; 287 /** @var SplFileInfo $fileinfo */ 288 foreach ($iterator as $fileinfo) { 289 if (!$fileinfo->isFile()) continue; 290 291 $path = $fileinfo->getRealPath(); // it should return "/" both on windows and linux 292 //remove dir part 293 $path = substr($path, strlen($datadir)); 294 //make file a dokuwiki path 295 $id = $this->pathID($path); 296 $pages[] = $id; 297 } 298 299 return $pages; 300 } 301 302 /** 303 * translates a document path to an ID 304 * 305 * fixes dokuwiki pathID - support for Windows enviroment 306 * 307 * @param string $path 308 * @param bool $keeptxt 309 * 310 * @return mixed|string 311 */ 312 public function pathID($path,$keeptxt=false){ 313 $id = utf8_decodeFN($path); 314 $id = str_replace(DIRECTORY_SEPARATOR,':',$id); 315 if(!$keeptxt) $id = preg_replace('#\.txt$#','',$id); 316 $id = trim($id, ':'); 317 return $id; 318 } 319} 320