xref: /plugin/structnotification/action/notification.php (revision a3d2920411ed6fdc34fba3a33b94a3ee70450482)
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