aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/globalsearch/GlobalSearchModule.php
blob: 7f06faf51219ba1a56cdcd84193b9ef7ffe4a937 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
<?php

/**
 * Abstract class GlobalSearchModule
 *
 * Module for global search extensions, e.g. forum, files or users
 *
 * @author      Thomas Hackl <thomas.hackl@uni-passau.de>
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
 * @category    Stud.IP
 * @since       4.1
 */
abstract class GlobalSearchModule
{
    /**
     * Employ generic cache methods. Be aware that this cache is shared among
     * the search modules so use indices properly:
     *
     * - user/:id for users
     * - range/:id for courses and institutes
     * - folder/id for folders
     *
     * Please add to this list if you add a new module that introduces a new
     * type.
     */
    use GlobalSearchCacheTrait;

    /**
     * Returns the displayname for this module
     *
     * @return string
     */
    abstract public static function getName();

    /**
     * Has to return a SQL Query that discovers all objects. All retrieved data is passed row by row to getGlobalSearchFilter.
     *
     * @param string $search the input query string
     * @param array $filter an array with search limiting filter information (e.g. 'category', 'semester', etc.)
     * @return string SQL Query to discover elements for the search
     */
    abstract public static function getSQL($search, $filter, $limit);

    /**
     * Returns an array of information for the found element. Following informations (key: description) are necessary
     *
     * - name: The name of the object
     * - url: The url to send the user to when he clicks the link
     *
     * Additional informations are:
     *
     * - additional: Subtitle for the hit
     * - expand: Url if the user further expands the search
     * - img: Avatar for the
     *
     * @param array $data One row returned from getSQL SQL Query
     * @param string $search The searchstring (Use for markup e.g. self::mark)
     * @return array Information Array
     */
    abstract public static function filter($data, $search);

    /**
     * Returns the filters that are displayed in the sidebar of the global search.
     *
     * @return array Filters for this class.
     */
    public static function getFilters()
    {
        return [];
    }

    /**
     * Returns the URL that can be called for a full search, containing
     * the specified category and the searchterm.
     *
     * Overwrite this method in your subclass to provide the category
     * specific search.
     *
     * @param string $searchterm what to search for?
     * @return string URL to the full search, containing the searchterm and the category
     */
    public static function getSearchURL($searchterm)
    {
        return '';
    }

    /**
     * Function to mark a querystring in a resultstring
     *
     * @param string $string             Result string that should be displayed
     * @param string $query              Query string to mark up
     * @param bool $longtext      Indicates whether result might be a longer text
     * @param bool|true $filename Indicates whether the query string might denote a file.
     * @return string
     */
    public static function mark($string, $query, $longtext = false, $filename = false)
    {
        // Secure
        $string = strip_tags($string);

        // Maximum length for an unshortened string.
        $maxlength = 100;

        if ($filename && mb_strpos($query, '/') !== false) {
            $args = explode('/', $query, 2);
            if ($args[1]) {
                return self::mark($string, trim($args[1]));
            }
            return self::mark($string, trim($args[0]));
        }

        $query = trim($query);

        // Replace direct string
        $quoted = preg_quote($query, '/');
        $result = preg_replace("/{$quoted}/Si", "<mark>$0</mark>", $string, -1, $found);

        if ($found) {
            // Check for overlength
            if ($longtext && mb_strlen($result) > $maxlength) {
                $start = max([0, mb_stripos($result, '<mark>') - 20]);
                return '[...]' . mb_substr($result, $start, $maxlength) . '[...]';
            }

            return $result;
        }

        // Replace camelcase
        $i = 1;
        $replacement = "${$i}";
        foreach (preg_split('//u', mb_strtoupper($query), -1, PREG_SPLIT_NO_EMPTY) as $letter) {
            $quoted = preg_quote($letter, '/');
            $queryletter[] = "({$quoted})";
            $replacement .= '<mark>$' . ++$i . '</mark>$' . ++$i;
        }

        $pattern = '/([\w\W]*)' . implode('([\w\W]*)', $queryletter) . '/S';
        $result = preg_replace($pattern, $replacement, $string, -1, $found);

        if ($found) {
            // Check for overlength
            if ($longtext && mb_strlen($result) > $maxlength) {
                $start = max([0, mb_stripos($result, '<mark>') - 20]);
                $space = mb_stripos($result, ' ', $start);
                $start = $space < $start + 20 ? $space : $start;
                return '[...]' . mb_substr($result, $start, $maxlength) . '[...]';
            }

            return $result;
        }

        // Check for overlength
        if ($longtext && mb_strlen($result) > $maxlength) {
            return '[...]' . mb_substr($string, 0, $maxlength) . '[...]';
        }

        if (mb_strlen($string) > $maxlength) {
            return mb_substr($string, 0, $maxlength) . '[...]';
        }

        return $string;
    }

    /**
    * Get the selected institute with sub-institutes as an array of IDs
    * or a single institute as a string to use in the SQL query.
    *
    * @param string $institute_id ID of the given institute or faculty
    * @return array: a single institute as string if selected
    *                or an array of institute IDs if a faculty was selected
    */
    public static function getInstituteIdsForSQL($institute_id)
    {
        $institutes = Institute::findByFaculty($institute_id);
        if ($institutes) {
            $institute_ids = array_column($institutes, 'Institut_id');
            $institute_ids[] = $institute_id;
            return $institute_ids;
        } else {
            return [$institute_id];
        }
    }

    /**
     * Get the selected seminar class with sub-types as an array
     * or a single seminar type as a string to use in an SQL query.
     *
     * @param string $sem_class a single sem_type ID or a sem_class containing multiple sem_types
     * @return string: seminar class/types formatted for an SQL query
     */
    public static function getSeminarTypesForSQL($sem_class)
    {
        $classes = SemClass::getClasses();
        if ($pos = strpos($sem_class, '_')) {
            // return just the sem_types.id (which is equal to seminare.status)
            return substr($sem_class, $pos + 1);
        } else {
            $type_ids = [];
            // return an array containing all sem_types belonging to the chosen sem_class
            $class = $classes[$sem_class];
            foreach ($class->getSemTypes() as $types_id => $types) {
                array_push($type_ids, $types['id']);
            }
            return $type_ids;
        }
    }

    /**
     * Get the current semester considering the given
     * SEMESTER_TIME_SWITCH in the CONFIG
     * (n weeks before the next semester)
     *
     * @return int The start time of the current semester.
     */
    public static function getCurrentSemester()
    {
        $sem_time_switch = Config::get()->SEMESTER_TIME_SWITCH;
        $current_semester = Semester::findByTimestamp(time() + $sem_time_switch * 7 * 24 * 3600);

        return (int)$current_semester['beginn'];
    }

    /**
     * Returns a list of all active search modules
     * @return array search_class => data
     */
    public static function getActiveSearchModules()
    {
        $modules = (array)array_filter(
            Config::get()->GLOBALSEARCH_MODULES,
            function ($data, $module) {
                if ($module === 'GlobalSearchModules' && !MVV::isVisibleSearch()) {
                    return false;
                }

                if (in_array($module, ['GlobalSearchResources', 'GlobalSearchRoomAssignments'])
                    && !Config::get()->RESOURCES_ENABLE
                ) {
                    return false;
                }

                return $data['active'] && class_exists($module, true);
            }, ARRAY_FILTER_USE_BOTH
        );

        return array_keys($modules);
    }
}