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