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