1<?php
2/**
3 * DokuWiki Plugin structtasks
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Chris MacMackin <cmacmackin@gmail.com>
7 *
8 */
9
10namespace dokuwiki\plugin\structtasks\meta;
11
12use dokuwiki\plugin\struct\types\Date;
13use dokuwiki\plugin\struct\types\DateTime;
14use dokuwiki\plugin\struct\types\Dropdown;
15use dokuwiki\plugin\struct\types\User;
16use dokuwiki\plugin\struct\types\Mail;
17use dokuwiki\plugin\struct\types\Text;
18
19/**
20 * Class with various useful static methods.
21 */
22class Utilities
23{
24    protected $struct;
25    /// Date-time formata for the due-date, extracted from the
26    /// most-recently validated schema and cached here.
27    protected $duedate_formats = [];
28
29    /**
30     *  Pass in an instance of the struct_helper plugin when building this class;
31     * @param mixed $helper
32     */
33    public function __construct($helper) {
34        $this->struct = $helper;
35    }
36
37    /**
38     * Tests whether the specified schema meets the requirements for
39     * describing tasks.
40     * @return bool
41     * @param mixed $schema
42     */
43    function isValidSchema($schema): bool {
44        $schemas_found = $this->struct->getSchema($schema);
45        $s = $schemas_found[$schema];
46        if ($s->getTimeStamp() == 0) {
47            msg("Schema '${schema}', needed for structtasks, does not exist.", -1);
48            return false;
49        }
50        $col_names = ['duedate', 'assignees', 'status'];
51        $col_types = [
52            'duedate' => [Date::class, DateTime::class],
53            'assignees' => [User::class, Mail::class],
54            'status' => [Dropdown::class, Text::class]
55        ];
56        $accepts_multi = [
57            'duedate' => false,
58            'assignees' => true,
59            'status' => false,
60        ];
61        $columns = [];
62        $valid = true;
63        foreach ($col_types as $name => $types) {
64            $col = $s->findColumn($name);
65            if ($col === false) {
66                msg("structtasks schema '$schema' has no column '$name'.", -1);
67                $valid = false;
68            } else {
69                $coltype = get_class($col->getType());
70                if (!in_array($coltype, $types)) {
71                    msg("Column '${name}' of structtasks schema '$schema' has invalid type ${coltype}", -1);
72                    $valid = false;
73                }
74                if ($accepts_multi[$name] xor $col->isMulti()) {
75                    msg("Column '${name}' of structtasks schema '$schema' must not accept multiple values; change the configurations", -1);
76                    $valid = false;
77                }
78            }
79        }
80        if ($valid) $this->duedate_formats[$schema] = $s->findColumn('duedate')->getType()->getConfig()['format'];
81        return $valid;
82    }
83
84    /**
85     * Gets the date format for the specified schema. Throws an error
86     * if schema is not valid for use by structtasks.
87     */
88    function dateFormat($schema) {
89        if (!array_key_exists($schema, $this->duedate_formats)) {
90            $this->isValidSchema($schema);
91        }
92        return $this->duedate_formats[$schema];
93    }
94
95    /**
96     * Safely fetches the old and new struct metadata for this
97     * event. Also returns a flag indicating if this page is even a
98     * valid task for which notifications could be sent.
99     */
100    function getMetadata($id, $schema, $old_rev, $new_rev) {
101        if (!$this->isValidSchema($schema)) {
102            return [NULL, NULL, false];
103        }
104        $old_data = $this->struct->getData($id, null, $old_rev);
105        $new_data = $this->struct->getData($id, null, $new_rev);
106        if (!array_key_exists($schema, $old_data) or !array_key_exists($schema, $new_data)) {
107            return [NULL, NULL, false];
108        }
109        $dateformat = $this->dateFormat($schema);
110        $old_data[$schema]['date_format'] =
111        $new_data[$schema]['date_format'] = $this->dateFormat($schema);
112        return [$this->formatData($old_data[$schema], $dateformat),
113                $this->formatData($new_data[$schema], $dateformat),
114                true];
115    }
116
117    /**
118     * Return a string with the real name and email address of $user,
119     * suitable for using to send them an email.
120     */
121    function getUserEmail($user) {
122        global $auth;
123        $userData = $auth->getUserData($user, false);
124        if ($userData === false) {
125            if (mail_isvalid($user)) {
126                return $user;
127            } else {
128                return '';
129            }
130        }
131        $realname = $userData['name'];
132        $email = $userData['mail'];
133        if ($realname === '') return $email;
134        if (strpos($userData['name'], ',')) $realname = '"' . $realname;
135        return "${realname} <${email}>";
136    }
137
138    /**
139     * Convert a list of usernames into a list of formatted email
140     * addresses.
141     */
142    function assigneesToEmails($assignees) {
143        if (!is_array($assignees)) {
144            $assignees = [$assignees];
145        }
146        return array_values(array_filter(array_map([$this, 'getUserEmail'], $assignees)));
147   }
148
149    /**
150     * Processes the data returned by the struct plugin to put it in a
151     * format useful for this plugin.
152     */
153    function formatData($structdata, $dateformat) {
154        if ($structdata['duedate'] !== '') {
155            $d = date_create($structdata['duedate']);
156            $df = $d->format($dateformat);
157        } else {
158            $d = null;
159            $df = '';
160        }
161        return [
162            'duedate' => $d,
163            'duedate_formatted' => $df,
164            'assignees' => $this->assigneesToEmails($structdata['assignees']),
165            'status' => $structdata['status'],
166        ];
167    }
168}
169