Passed
Push — master ( 23a9df...d6697c )
by
unknown
05:58 queued 15s
created

IndexSqlite::getWhereFolderids()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 12
rs 10
1
<?php
2
3
define('PRIVATE_FID_ROOT', 0x1);
4
5
define('PR_FOLDER_ID', 0x67480014);
6
define('PR_MID', 0x674A0014);
7
define('PR_CHANGE_NUMBER', 0x67A40014);
8
9
class IndexSqlite extends SQLite3 {
10
	private $username;
11
	private $count;
12
	private $store;
13
	private $session;
14
15
	private static function get_gc_value($eid) {
16
		$r0 = ($eid >> 56) & 0xFF;
17
		$r1 = ($eid >> 48) & 0xFF;
18
		$r2 = ($eid >> 40) & 0xFF;
19
		$r3 = ($eid >> 32) & 0xFF;
20
		$r4 = ($eid >> 24) & 0xFF;
21
		$r5 = ($eid >> 16) & 0xFF;
22
		$value = $r0 | ($r1 << 8) | ($r2 << 16) | ($r3 << 24) | ($r4 << 32) | ($r5 << 40);
23
24
		return $value;
25
	}
26
27
	public function __construct($username = null, $session = null, $store = null) {
28
		$this->username = $username ?? $GLOBALS["mapisession"]->getSMTPAddress();
29
		$this->session = $session ?? $GLOBALS["mapisession"]->getSession();
30
		$this->store = $store ?? $GLOBALS["mapisession"]->getDefaultMessageStore();
31
		$this->open(SQLITE_INDEX_PATH . '/' . $this->username . '/index.sqlite3');
32
	}
33
34
	private function try_insert_content(
35
		$search_entryid,
36
		$row,
37
		$message_classes,
38
		$date_start,
39
		$date_end,
40
		$unread,
41
		$has_attachments
42
	) {
43
		// if match condition contains '@', $row['entryid'] will disappear. it seems a bug for php-sqlite
44
		if (empty($row['entryid'])) {
45
			$results = $this->query("SELECT entryid FROM msg_content WHERE message_id=" . $row['message_id']);
46
			$row1 = $results->fetchArray(SQLITE3_NUM);
47
			if ($row1 && !empty($row1[0])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row1 of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
48
				$row['entryid'] = $row1[0];
49
			}
50
			// abort if the entryid is not available
51
			else {
52
				error_log(sprintf("No entryid available, not possible to link the message %d.", $row['message_id']));
53
				return;
54
			}
55
		}
56
		if (isset($message_classes)) {
57
			$found = false;
58
			foreach ($message_classes as $message_class) {
59
				if (strncasecmp($row['message_class'], $message_class, strlen($message_class)) == 0) {
60
					$found = true;
61
					break;
62
				}
63
			}
64
			if (!$found) {
65
				return;
66
			}
67
		}
68
		if (isset($date_start) && $row['date'] < $date_start) {
69
			return;
70
		}
71
		if (isset($date_end) && $row['date'] > $date_end) {
72
			return;
73
		}
74
		if (isset($unread) && $row['readflag']) {
75
			return;
76
		}
77
		if (isset($has_attachments) && !$row['attach_indexed']) {
78
			return;
79
		}
80
81
		try {
82
			mapi_linkmessage($this->session, $search_entryid, $row['entryid']);
83
		}
84
		catch (Exception $e) {
85
			return;
86
		}
87
		++$this->count;
88
	}
89
90
	private function result_full() {
91
		return $this->count >= MAX_FTS_RESULT_ITEMS;
92
	}
93
94
	public function search($search_entryid, $search_patterns, $folder_entryid, $recursive) {
95
		$whereFolderids = '';
96
		if (isset($folder_entryid)) {
97
			try {
98
				$folder = mapi_msgstore_openentry($this->store, $folder_entryid);
99
				if (!$folder) {
100
					return false;
101
				}
102
				$tmp_props = mapi_getprops($folder, [PR_FOLDER_ID]);
103
				if (empty($tmp_props[PR_FOLDER_ID])) {
104
					return false;
105
				}
106
				$folder_id = IndexSqlite::get_gc_value((int) $tmp_props[PR_FOLDER_ID]);
107
				$whereFolderids .= "c.folder_id in (" . $folder_id . ", ";
108
				if ($recursive) {
109
					$this->getWhereFolderids($folder, $whereFolderids);
110
				}
111
				$whereFolderids = substr($whereFolderids, 0, -2) . ") AND ";
112
			}
113
			catch (Exception $e) {
114
				error_log(sprintf("Index: error getting folder information %s - %s", $this->username, $e));
115
116
				return false;
117
			}
118
		}
119
		$sql_string = "SELECT c.message_id, c.entryid, c.folder_id, " .
120
			"c.message_class, c.date, c.readflag, c.attach_indexed " .
121
			"FROM msg_content c " .
122
			"JOIN messages m ON c.message_id = m.rowid " .
123
			"WHERE ";
124
		if (!empty($whereFolderids)) {
125
			$sql_string .= $whereFolderids;
126
		}
127
		$sql_string .= "messages MATCH '";
128
		$this->count = 0;
129
		// Extract search_patterns into separate variables
130
		[
131
			'sender' => $sender,
132
			'sending' => $sending,
133
			'recipients' => $recipients,
134
			'subject' => $subject,
135
			'content' => $content,
136
			'attachments' => $attachments,
137
			'others' => $others,
138
			'message_classes' => $message_classes,
139
			'date_start' => $date_start,
140
			'date_end' => $date_end,
141
			'unread' => $unread,
142
			'has_attachments' => $has_attachments,
143
			'categories' => $categories,
144
		] = $search_patterns;
145
		if (isset($sender) && $sender == $sending && $sending == $recipients && $recipients == $subject &&
146
			$subject == $content && $content == $attachments && $attachments == $others && empty($categories)) {
147
			$sql_string .= SQLite3::escapeString($this->quote_words($sender)) . "'";
148
		}
149
		else {
150
			$first = true;
151
			foreach ($search_patterns as $key => $search_pattern) {
152
				switch($key) {
153
					case 'message_classes':
154
					case 'date_start':
155
					case 'date_end':
156
					case 'unread':
157
					case 'has_attachments':
158
					case 'categories':
159
						break;
160
					default:
161
						if (!is_null($search_pattern)) {
162
							if ($first === true) {
163
								$first = false;
164
							}
165
							else {
166
								$sql_string .= " OR ";
167
							}
168
							$sql_string .= $key . ':' . SQLite3::escapeString($this->quote_words($search_pattern));
169
						}
170
				}
171
			}
172
			if ($first) {
173
				return false;
174
			}
175
			$sql_string .= "'";
176
			if (!empty($categories)) {
177
				foreach ($categories as $category) {
178
					$sql_string .= " AND messages MATCH 'others:" . SQLite3::escapeString($this->quote_words($category)) . "'";
179
				}
180
			}
181
		}
182
		$sql_string .= " ORDER BY c.date DESC LIMIT " . MAX_FTS_RESULT_ITEMS;
183
		$results = $this->query($sql_string);
184
		while (($row = $results->fetchArray(SQLITE3_ASSOC)) && !$this->result_full()) {
185
			$this->try_insert_content(
186
				$search_entryid,
187
				$row,
188
				$message_classes,
189
				$date_start,
190
				$date_end,
191
				$unread,
192
				$has_attachments
193
			);
194
		}
195
196
		return true;
197
	}
198
199
	private function quote_words($search_string) {
200
		return '"' . preg_replace("/(\\s+)/", '*" "', trim($search_string)) . '"*';
201
	}
202
203
	/**
204
	 * Returns the restriction to filter hidden folders.
205
	 *
206
	 * @return array
207
	 */
208
	private function getHiddenRestriction() {
209
		return
210
			[RES_OR, [
211
				[RES_PROPERTY,
212
					[
213
						RELOP => RELOP_EQ,
214
						ULPROPTAG => PR_ATTR_HIDDEN,
215
						VALUE => [PR_ATTR_HIDDEN => false],
216
					],
217
				],
218
				[RES_NOT,
219
					[
220
						[RES_EXIST,
221
							[
222
								ULPROPTAG => PR_ATTR_HIDDEN,
223
							],
224
						],
225
					],
226
				],
227
			]];
228
	}
229
230
	/**
231
	 * Returns the comma joined folderids for the WHERE clause in the SQL
232
	 * statement.
233
	 *
234
	 * @param mixed  $folder
235
	 * @param string $whereFolderids
236
	 */
237
	private function getWhereFolderids($folder, &$whereFolderids) {
238
		/**
239
		 * remove hidden folders, folders with PR_ATTR_HIDDEN property set
240
		 * should not be shown to the client.
241
		 */
242
		$restriction = $this->getHiddenRestriction();
243
		$hierarchy = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);
244
		mapi_table_restrict($hierarchy, $restriction, TBL_BATCH);
245
		$rows = mapi_table_queryallrows($hierarchy, [PR_FOLDER_ID]);
246
		foreach ($rows as $row) {
247
			if (isset($row[PR_FOLDER_ID])) {
248
				$whereFolderids .= IndexSqlite::get_gc_value((int) $row[PR_FOLDER_ID]) . ", ";
249
			}
250
		}
251
	}
252
}
253