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 $result = $search->execute(); 113 $result_pids = $search->getPids(); 114 /* @var Value[] $row */ 115 $counter = count($result); 116 117 /* @var Value[] $row */ 118 for ($i = 0; $i < $counter; $i++) { 119 $values = $result[$i]; 120 $pid = $result_pids[$i]; 121 122 $users_set = $this->usersSet($users_and_groups, $values); 123 if (!isset($users_set[$user])) continue; 124 125 $rawDate = $this->getValueByLabel($values, $field); 126 if ($this->predicateTrue($rawDate, $operator, $value)) { 127 $message_with_replacements = $this->replacePlaceholders($message, $values); 128 $message_with_replacements_html = p_render( 129 'xhtml', 130 p_get_instructions($message_with_replacements), 131 $info 132 ); 133 $event->data['notifications'][] = [ 134 'plugin' => 'structnotification', 135 'id' => $predicate['id'] . ':' . $schema . ':' . $pid . ':' . $rawDate, 136 'full' => $message_with_replacements_html, 137 'brief' => $message_with_replacements_html, 138 'timestamp' => (int) strtotime($rawDate) 139 ]; 140 } 141 } 142 } catch (Exception $e) { 143 msg($e->getMessage(), -1); 144 return; 145 } 146 } 147 } 148 149 /** 150 * @return array 151 */ 152 protected function usersSet($user_and_groups, $values) 153 { 154 /** @var DokuWiki_Auth_Plugin $auth */ 155 global $auth; 156 157 // $auth is missing in CLI context 158 if (is_null($auth)) { 159 auth_setup(); 160 } 161 162 //make substitutions 163 $user_and_groups = preg_replace_callback( 164 '/@@(.*?)@@/', 165 function ($matches) use ($values) { 166 [$schema, $field] = explode('.', trim($matches[1])); 167 if (!$field) return ''; 168 /* @var Value $value */ 169 foreach ($values as $value) { 170 $column = $value->getColumn(); 171 $colLabel = $column->getLabel(); 172 $type = $column->getType(); 173 if ($colLabel == $field) { 174 if ( 175 class_exists('\dokuwiki\plugin\structgroup\types\Group') && 176 $type instanceof Group 177 ) { 178 if ($column->isMulti()) { 179 return implode( 180 ',', 181 array_map(static fn($rawValue) => '@' . $rawValue, $value->getRawValue()) 182 ); 183 } else { 184 return '@' . $value->getRawValue(); 185 } 186 } 187 if ($column->isMulti()) { 188 return implode(',', $value->getRawValue()); 189 } else { 190 return $value->getRawValue(); 191 } 192 } 193 } 194 return ''; 195 }, 196 $user_and_groups 197 ); 198 199 $user_and_groups_set = array_map('trim', explode(',', $user_and_groups)); 200 $users = []; 201 $groups = []; 202 foreach ($user_and_groups_set as $user_or_group) { 203 if ($user_or_group[0] == '@') { 204 $groups[] = substr($user_or_group, 1); 205 } else { 206 $users[] = $user_or_group; 207 } 208 } 209 $set = []; 210 211 $all_users = $auth->retrieveUsers(); 212 foreach ($all_users as $user => $info) { 213 if (in_array($user, $users)) { 214 $set[$user] = $info; 215 } elseif (array_intersect($groups, $info['grps'])) { 216 $set[$user] = $info; 217 } 218 } 219 220 return $set; 221 } 222 223 protected function predicateTrue($date, $operator, $value) 224 { 225 $date = date('Y-m-d', strtotime($date)); 226 227 switch ($operator) { 228 case 'before': 229 $days = date('Y-m-d', strtotime("+$value days")); 230 return $days >= $date; 231 case 'after': 232 $days = date('Y-m-d', strtotime("-$value days")); 233 return $date <= $days; 234 case 'at': 235 $now = new DateTime(); 236 $at = new DateTime(date($value, strtotime($date))); 237 return $now >= $at; 238 default: 239 return false; 240 } 241 } 242 243 protected function replacePlaceholders($message, $values) 244 { 245 $patterns = []; 246 $replacements = []; 247 /* @var Value $value */ 248 foreach ($values as $value) { 249 $schema = $value->getColumn()->getTable(); 250 $label = $value->getColumn()->getLabel(); 251 $patterns[] = "/@@$schema.$label@@/"; 252 $replacements[] = $value->getDisplayValue(); 253 } 254 255 return preg_replace($patterns, $replacements, $message); 256 } 257 258 /** 259 * @param Search $search 260 * @param string $filters 261 */ 262 protected function addFiltersToSearch(&$search, $filters) 263 { 264 if (!$filters) return; 265 266 /** @var \helper_plugin_struct_config $confHelper */ 267 $confHelper = plugin_load('helper', 'struct_config'); 268 269 $filterConfigs = explode("\r\n", $filters); 270 271 foreach ($filterConfigs as $config) { 272 [$colname, $comp, $value, ] = $confHelper->parseFilterLine('AND', $config); 273 $search->addFilter($colname, $value, $comp, 'AND'); 274 } 275 } 276} 277