1<?php
2// messageset.php -- HotCRP sets of messages by fields
3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE.
4
5class MessageSet {
6    public $ignore_msgs = false;
7    private $allow_error;
8    private $werror;
9    private $errf;
10    private $msgs;
11    private $canonfield;
12    public $has_warning;
13    public $has_error;
14
15    const INFO = 0;
16    const WARNING = 1;
17    const ERROR = 2;
18
19    function __construct() {
20        $this->clear();
21    }
22    function clear_messages() {
23        $this->errf = $this->msgs = [];
24        $this->has_warning = $this->has_error = 0;
25    }
26    function clear() {
27        $this->clear_messages();
28    }
29
30    function translate_field($src, $dst) {
31        $this->canonfield[$src] = $this->canonical_field($dst);
32    }
33    function canonical_field($field) {
34        if ($field && $this->canonfield && isset($this->canonfield[$field]))
35            $field = $this->canonfield[$field];
36        return $field;
37    }
38    function allow_error_at($field, $set = null) {
39        $field = $this->canonical_field($field);
40        if ($set === null)
41            return $this->allow_error && isset($this->allow_error[$field]);
42        else if ($set)
43            $this->allow_error[$field] = true;
44        else if ($this->allow_error)
45            unset($this->allow_error[$field]);
46    }
47    function werror_at($field, $set = null) {
48        $field = $this->canonical_field($field);
49        if ($set === null)
50            return $this->werror && isset($this->werror[$field]);
51        else if ($set)
52            $this->werror[$field] = true;
53        else if ($this->werror)
54            unset($this->werror[$field]);
55    }
56
57    function msg($field, $msg, $status) {
58        if ($this->ignore_msgs)
59            return;
60        $this->canonfield && ($field = $this->canonical_field($field));
61        if ($status == self::WARNING
62            && $field && $this->werror && isset($this->werror[$field]))
63            $status = self::ERROR;
64        if ($field)
65            $this->errf[$field] = max(get($this->errf, $field, 0), $status);
66        if ($msg)
67            $this->msgs[] = [$field, $msg, $status];
68        if ($status == self::WARNING)
69            ++$this->has_warning;
70        if ($status == self::ERROR
71            && !($field && $this->allow_error && isset($this->allow_error[$field])))
72            ++$this->has_error;
73    }
74    function error_at($field, $msg) {
75        $this->msg($field, $msg, self::ERROR);
76    }
77    function warning_at($field, $msg) {
78        $this->msg($field, $msg, self::WARNING);
79    }
80    function info_at($field, $msg) {
81        $this->msg($field, $msg, self::INFO);
82    }
83
84    function has_error() {
85        return $this->has_error > 0;
86    }
87    function nerrors() {
88        return $this->has_error;
89    }
90    function has_warning() {
91        return $this->has_warning > 0;
92    }
93    function nwarnings() {
94        return $this->has_warning;
95    }
96    function has_problem() {
97        return $this->has_warning > 0 || $this->has_error > 0;
98    }
99    function has_messages() {
100        return !empty($this->msgs);
101    }
102    function problem_status() {
103        if ($this->has_error > 0)
104            return self::ERROR;
105        else
106            return $this->has_warning > 0 ? self::WARNING : self::INFO;
107    }
108    function has_error_at($field) {
109        $this->canonfield && ($field = $this->canonical_field($field));
110        return get($this->errf, $field, 0) > 1;
111    }
112    function has_problem_at($field) {
113        $this->canonfield && ($field = $this->canonical_field($field));
114        return get($this->errf, $field, 0) > 0;
115    }
116    function problem_status_at($field) {
117        $this->canonfield && ($field = $this->canonical_field($field));
118        return get($this->errf, $field, 0);
119    }
120    function control_class($field, $rest = "") {
121        $x = $field ? get($this->errf, $field, 0) : 0;
122        if ($x >= self::ERROR)
123            return $rest === "" ? "has-error" : $rest . " has-error";
124        else if ($x === self::WARNING)
125            return $rest === "" ? "has-warning" : $rest . " has-warning";
126        else
127            return $rest;
128    }
129
130    static private function filter_msgs($ms, $include_fields) {
131        if ($include_fields || empty($ms))
132            return $ms ? : [];
133        else
134            return array_map(function ($mx) { return $mx[1]; }, $ms);
135    }
136    function message_field_map() {
137        return $this->errf;
138    }
139    function message_fields() {
140        return array_keys($this->errf);
141    }
142    function error_fields() {
143        if (!$this->has_error)
144            return [];
145        return array_keys(array_filter($this->errf, function ($v) { return $v >= self::ERROR; }));
146    }
147    function problem_fields() {
148        return array_keys(array_filter($this->errf, function ($v) { return $v >= self::WARNING; }));
149    }
150    function messages($include_fields = false) {
151        return self::filter_msgs($this->msgs, $include_fields);
152    }
153    function errors($include_fields = false) {
154        if (!$this->has_error)
155            return [];
156        $ms = array_filter($this->msgs, function ($mx) { return $mx[2] >= self::ERROR; });
157        return self::filter_msgs($ms, $include_fields);
158    }
159    function warnings($include_fields = false) {
160        if (!$this->has_warning)
161            return [];
162        $ms = array_filter($this->msgs, function ($mx) { return $mx[2] == self::WARNING; });
163        return self::filter_msgs($ms, $include_fields);
164    }
165    function messages_at($field, $include_fields = false) {
166        if (empty($this->msgs) || !isset($this->errf[$field]))
167            return [];
168        $this->canonfield && ($field = $this->canonical_field($field));
169        $ms = array_filter($this->msgs, function ($mx) use ($field) { return $mx[0] === $field; });
170        if ($include_fields)
171            return $ms;
172        else
173            return array_map(function ($mx) { return $mx[1]; }, $ms);
174    }
175}
176