1<?php 2 3/** 4 * DokuWiki Plugin structnotification (Action Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author Szymon Olewniczak <it@rid.pl> 8 */ 9 10use dokuwiki\Extension\ActionPlugin; 11use dokuwiki\Extension\EventHandler; 12use dokuwiki\Extension\Event; 13use dokuwiki\plugin\structgroup\types\Group; 14use dokuwiki\plugin\struct\meta\Search; 15use dokuwiki\plugin\struct\meta\Value; 16 17class action_plugin_structnotification_notification extends ActionPlugin 18{ 19 /** 20 * Registers a callback function for a given event 21 * 22 * @param EventHandler $controller DokuWiki's event controller object 23 * 24 * @return void 25 */ 26 public function register(EventHandler $controller) 27 { 28 $controller->register_hook('PLUGIN_NOTIFICATION_REGISTER_SOURCE', 'AFTER', $this, 'addNotificationsSource'); 29 $controller->register_hook('PLUGIN_NOTIFICATION_GATHER', 'AFTER', $this, 'addNotifications'); 30 $controller->register_hook( 31 'PLUGIN_NOTIFICATION_CACHE_DEPENDENCIES', 32 'AFTER', 33 $this, 34 'addNotificationCacheDependencies' 35 ); 36 } 37 38 public function addNotificationsSource(Event $event) 39 { 40 $event->data[] = 'structnotification'; 41 } 42 43 public function addNotificationCacheDependencies(Event $event) 44 { 45 if (!in_array('structnotification', $event->data['plugins'])) return; 46 47 try { 48 /** @var \helper_plugin_structnotification_db $db_helper */ 49 $db_helper = plugin_load('helper', 'structnotification_db'); 50 $sqlite = $db_helper->getDB(); 51 $event->data['dependencies'][] = $sqlite->getAdapter()->getDbFile(); 52 } catch (Exception $e) { 53 msg($e->getMessage(), -1); 54 return; 55 } 56 } 57 58 protected function getValueByLabel($values, $label) 59 { 60 /* @var Value $value */ 61 foreach ($values as $value) { 62 $colLabel = $value->getColumn()->getLabel(); 63 if ($colLabel == $label) { 64 return $value->getRawValue(); 65 } 66 } 67 //nothing found 68 throw new Exception("column: $label not found in values"); 69 } 70 71 public function addNotifications(Event $event) 72 { 73 if (!in_array('structnotification', $event->data['plugins'])) return; 74 75 try { 76 /** @var \helper_plugin_structnotification_db$db_helper */ 77 $db_helper = plugin_load('helper', 'structnotification_db'); 78 $sqlite = $db_helper->getDB(); 79 } catch (Exception $e) { 80 msg($e->getMessage(), -1); 81 return; 82 } 83 84 $user = $event->data['user']; 85 86 $q = 'SELECT * FROM predicate'; 87 $res = $sqlite->query($q); 88 89 $predicates = $sqlite->res2arr($res); 90 91 foreach ($predicates as $predicate) { 92 $schema = $predicate['schema']; 93 $field = $predicate['field']; 94 $operator = $predicate['operator']; 95 $value = $predicate['value']; 96 $filters = $predicate['filters']; 97 $users_and_groups = $predicate['users_and_groups']; 98 $message = $predicate['message']; 99 100 try { 101 $search = new Search(); 102 foreach (explode(',', $schema) as $table) { 103 $search->addSchema($table); 104 $search->addColumn($table . '.*'); 105 } 106 // add special columns 107 $special_columns = ['%pageid%', '%title%', '%lastupdate%', '%lasteditor%', '%lastsummary%', '%rowid%']; 108 foreach ($special_columns as $special_column) { 109 $search->addColumn($special_column); 110 } 111 $this->addFiltersToSearch($search, $filters); 112 113 // temporary workaround for GETACCESSLEVEL check in struct queries in closed wikis: 114 // cli user has undefined permissions, so we disable ACLs to get any results at all 115 if (PHP_SAPI === 'cli') { 116 global $conf; 117 $origACL = $conf['useacl']; 118 $conf['useacl'] = false; 119 } 120 121 $result = $search->getRows(); 122 $result_pids = $search->getPids(); 123 124 // restore ACL setting 125 if (PHP_SAPI === 'cli') { 126 $conf['useacl'] = $origACL; 127 } 128 129 /* @var Value[] $row */ 130 $counter = count($result); 131 132 /* @var Value[] $row */ 133 for ($i = 0; $i < $counter; $i++) { 134 $values = $result[$i]; 135 $pid = $result_pids[$i]; 136 137 $users_set = $this->usersSet($users_and_groups, $values); 138 if (!isset($users_set[$user])) continue; 139 140 $rawDate = $this->getValueByLabel($values, $field); 141 if ($this->predicateTrue($rawDate, $operator, $value)) { 142 $message_with_replacements = $this->replacePlaceholders($message, $values); 143 $message_with_replacements_html = p_render( 144 'xhtml', 145 p_get_instructions($message_with_replacements), 146 $info 147 ); 148 $event->data['notifications'][] = [ 149 'plugin' => 'structnotification', 150 'id' => $predicate['id'] . ':' . $schema . ':' . $pid . ':' . $rawDate, 151 'full' => $message_with_replacements_html, 152 'brief' => $message_with_replacements_html, 153 'timestamp' => (int) strtotime($rawDate) 154 ]; 155 } 156 } 157 } catch (Exception $e) { 158 msg($e->getMessage(), -1); 159 return; 160 } 161 } 162 } 163 164 /** 165 * @return array 166 */ 167 protected function usersSet($user_and_groups, $values) 168 { 169 /** @var DokuWiki_Auth_Plugin $auth */ 170 global $auth; 171 172 // $auth is missing in CLI context 173 if (is_null($auth)) { 174 auth_setup(); 175 } 176 177 //make substitutions 178 $user_and_groups = preg_replace_callback( 179 '/@@(.*?)@@/', 180 function ($matches) use ($values) { 181 [$schema, $field] = explode('.', trim($matches[1])); 182 if (!$field) return ''; 183 /* @var Value $value */ 184 foreach ($values as $value) { 185 $column = $value->getColumn(); 186 $colLabel = $column->getLabel(); 187 $type = $column->getType(); 188 if ($colLabel == $field) { 189 if ( 190 class_exists('\dokuwiki\plugin\structgroup\types\Group') && 191 $type instanceof Group 192 ) { 193 if ($column->isMulti()) { 194 return implode( 195 ',', 196 array_map(static fn($rawValue) => '@' . $rawValue, $value->getRawValue()) 197 ); 198 } else { 199 return '@' . $value->getRawValue(); 200 } 201 } 202 if ($column->isMulti()) { 203 return implode(',', $value->getRawValue()); 204 } else { 205 return $value->getRawValue(); 206 } 207 } 208 } 209 return ''; 210 }, 211 $user_and_groups 212 ); 213 214 $user_and_groups_set = array_map('trim', explode(',', $user_and_groups)); 215 $users = []; 216 $groups = []; 217 foreach ($user_and_groups_set as $user_or_group) { 218 if ($user_or_group[0] == '@') { 219 $groups[] = substr($user_or_group, 1); 220 } else { 221 $users[] = $user_or_group; 222 } 223 } 224 $set = []; 225 226 $all_users = $auth->retrieveUsers(); 227 foreach ($all_users as $user => $info) { 228 if (in_array($user, $users)) { 229 $set[$user] = $info; 230 } elseif (array_intersect($groups, $info['grps'])) { 231 $set[$user] = $info; 232 } 233 } 234 235 return $set; 236 } 237 238 protected function predicateTrue($date, $operator, $value) 239 { 240 $date = date('Y-m-d', strtotime($date)); 241 242 switch ($operator) { 243 case 'before': 244 $days = date('Y-m-d', strtotime("+$value days")); 245 return $days >= $date; 246 case 'after': 247 $days = date('Y-m-d', strtotime("-$value days")); 248 return $date <= $days; 249 case 'at': 250 $now = new DateTime(); 251 $at = new DateTime(date($value, strtotime($date))); 252 return $now >= $at; 253 default: 254 return false; 255 } 256 } 257 258 protected function replacePlaceholders($message, $values) 259 { 260 $patterns = []; 261 $replacements = []; 262 /* @var Value $value */ 263 foreach ($values as $value) { 264 $schema = $value->getColumn()->getTable(); 265 $label = $value->getColumn()->getLabel(); 266 $patterns[] = "/@@$schema.$label@@/"; 267 $replacements[] = $value->getDisplayValue(); 268 } 269 270 return preg_replace($patterns, $replacements, $message); 271 } 272 273 /** 274 * @param Search $search 275 * @param string $filters 276 */ 277 protected function addFiltersToSearch(&$search, $filters) 278 { 279 if (!$filters) return; 280 281 /** @var \helper_plugin_struct_config $confHelper */ 282 $confHelper = plugin_load('helper', 'struct_config'); 283 284 $filterConfigs = explode("\r\n", $filters); 285 286 foreach ($filterConfigs as $config) { 287 [$colname, $comp, $value, ] = $confHelper->parseFilterLine('AND', $config); 288 $search->addFilter($colname, $value, $comp, 'AND'); 289 } 290 } 291} 292