blob: 9af7ad751f1be7f774f9ee021c1e9e3a40c68ca4 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace LdapRecord;
4
5use Closure;
6use ErrorException;
7use Exception;
8
9trait HandlesConnection
10{
11 /**
12 * The LDAP host that is currently connected.
13 *
14 * @var string|null
15 */
16 protected $host;
17
18 /**
19 * The LDAP connection resource.
20 *
21 * @var resource|null
22 */
23 protected $connection;
24
25 /**
26 * The bound status of the connection.
27 *
28 * @var bool
29 */
30 protected $bound = false;
31
32 /**
33 * Whether the connection must be bound over SSL.
34 *
35 * @var bool
36 */
37 protected $useSSL = false;
38
39 /**
40 * Whether the connection must be bound over TLS.
41 *
42 * @var bool
43 */
44 protected $useTLS = false;
45
46 /**
47 * @inheritdoc
48 */
49 public function isUsingSSL()
50 {
51 return $this->useSSL;
52 }
53
54 /**
55 * @inheritdoc
56 */
57 public function isUsingTLS()
58 {
59 return $this->useTLS;
60 }
61
62 /**
63 * @inheritdoc
64 */
65 public function isBound()
66 {
67 return $this->bound;
68 }
69
70 /**
71 * @inheritdoc
72 */
73 public function isConnected()
74 {
75 return ! is_null($this->connection);
76 }
77
78 /**
79 * @inheritdoc
80 */
81 public function canChangePasswords()
82 {
83 return $this->isUsingSSL() || $this->isUsingTLS();
84 }
85
86 /**
87 * @inheritdoc
88 */
89 public function ssl($enabled = true)
90 {
91 $this->useSSL = $enabled;
92
93 return $this;
94 }
95
96 /**
97 * @inheritdoc
98 */
99 public function tls($enabled = true)
100 {
101 $this->useTLS = $enabled;
102
103 return $this;
104 }
105
106 /**
107 * @inheritdoc
108 */
109 public function setOptions(array $options = [])
110 {
111 foreach ($options as $option => $value) {
112 $this->setOption($option, $value);
113 }
114 }
115
116 /**
117 * @inheritdoc
118 */
119 public function getHost()
120 {
121 return $this->host;
122 }
123
124 /**
125 * @inheritdoc
126 */
127 public function getConnection()
128 {
129 return $this->connection;
130 }
131
132 /**
133 * @inheritdoc
134 */
135 public function getProtocol()
136 {
137 return $this->isUsingSSL() ? LdapInterface::PROTOCOL_SSL : LdapInterface::PROTOCOL;
138 }
139
140 /**
141 * @inheritdoc
142 */
143 public function getExtendedError()
144 {
145 return $this->getDiagnosticMessage();
146 }
147
148 /**
149 * Convert warnings to exceptions for the given operation.
150 *
151 * @param Closure $operation
152 *
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200153 * @return mixed
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100154 *
155 * @throws LdapRecordException
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200156 */
157 protected function executeFailableOperation(Closure $operation)
158 {
159 // If some older versions of PHP, errors are reported instead of throwing
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100160 // exceptions, which could be a significant detriment to our application.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200161 // Here, we will enforce these operations to throw exceptions instead.
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100162 set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200163 if (! $this->shouldBypassError($message)) {
164 throw new ErrorException($message, $severity, $severity, $file, $line);
165 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100166
167 return true;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200168 });
169
170 try {
171 if (($result = $operation()) !== false) {
172 return $result;
173 }
174
175 // If the failed query operation was a based on a query being executed
176 // -- such as a search, read, or listing, then we can safely return
177 // the failed response here and prevent throwning an exception.
178 if ($this->shouldBypassFailure($method = debug_backtrace()[1]['function'])) {
179 return $result;
180 }
181
182 throw new Exception("LDAP operation [$method] failed.");
183 } catch (ErrorException $e) {
184 throw LdapRecordException::withDetailedError($e, $this->getDetailedError());
185 } finally {
186 restore_error_handler();
187 }
188 }
189
190 /**
191 * Determine if the failed operation should be bypassed.
192 *
193 * @param string $method
194 *
195 * @return bool
196 */
197 protected function shouldBypassFailure($method)
198 {
199 return in_array($method, ['search', 'read', 'listing']);
200 }
201
202 /**
203 * Determine if the error should be bypassed.
204 *
205 * @param string $error
206 *
207 * @return bool
208 */
209 protected function shouldBypassError($error)
210 {
211 return $this->causedByPaginationSupport($error) || $this->causedBySizeLimit($error) || $this->causedByNoSuchObject($error);
212 }
213
214 /**
215 * Determine if the current PHP version supports server controls.
216 *
217 * @deprecated since v2.5.0
218 *
219 * @return bool
220 */
221 public function supportsServerControlsInMethods()
222 {
223 return version_compare(PHP_VERSION, '7.3.0') >= 0;
224 }
225
226 /**
227 * Generates an LDAP connection string for each host given.
228 *
229 * @param string|array $hosts
230 * @param string $port
231 *
232 * @return string
233 */
234 protected function makeConnectionUris($hosts, $port)
235 {
236 // If an attempt to connect via SSL protocol is being performed,
237 // and we are still using the default port, we will swap it
238 // for the default SSL port, for developer convenience.
239 if ($this->isUsingSSL() && $port == LdapInterface::PORT) {
240 $port = LdapInterface::PORT_SSL;
241 }
242
243 // The blank space here is intentional. PHP's LDAP extension
244 // requires additional hosts to be seperated by a blank
245 // space, so that it can parse each individually.
246 return implode(' ', $this->assembleHostUris($hosts, $port));
247 }
248
249 /**
250 * Assemble the host URI strings.
251 *
252 * @param array|string $hosts
253 * @param string $port
254 *
255 * @return array
256 */
257 protected function assembleHostUris($hosts, $port)
258 {
259 return array_map(function ($host) use ($port) {
260 return "{$this->getProtocol()}{$host}:{$port}";
261 }, (array) $hosts);
262 }
263}