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