StateObject::__isset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Simple data object with some PHP magic
9
 */
10
11
class StateObject implements JsonSerializable {
12
	private $SO_internalid;
13
	protected $data = [];
14
	protected $unsetdata = [];
15
	protected $changed = false;
16
17
	/**
18
	 * Returns the unique id of that data object.
19
	 *
20
	 * @return array
21
	 */
22
	public function GetID() {
23
		if (!isset($this->SO_internalid)) {
24
			$this->SO_internalid = sprintf('%04x%04x%04x', mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF));
25
		}
26
27
		return $this->SO_internalid;
28
	}
29
30
	/**
31
	 * Indicates if the data contained in this object was modified.
32
	 *
33
	 * @return array
34
	 */
35
	public function IsDataChanged() {
36
		return $this->changed;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->changed returns the type boolean which is incompatible with the documented return type array.
Loading history...
37
	}
38
39
	/**
40
	 * PHP magic to set an instance variable.
41
	 *
42
	 * @param mixed $name
43
	 * @param mixed $value
44
	 */
45
	public function __set($name, $value) {
46
		$lname = strtolower((string) $name);
47
		if (isset($this->data[$lname]) &&
48
				(
49
					(is_scalar($value) && !is_array($value) && $this->data[$lname] === $value) ||
50
					(is_array($value) && is_array($this->data[$lname]) && $this->data[$lname] === $value)
51
				)) {
52
			return false;
53
		}
54
55
		$this->data[$lname] = $value;
56
		$this->changed = true;
57
	}
58
59
	/**
60
	 * PHP magic to get an instance variable
61
	 * if the variable was not set previously, the value of the
62
	 * Unsetdata array is returned.
63
	 *
64
	 * @param mixed $name
65
	 */
66
	public function __get($name) {
67
		$lname = strtolower((string) $name);
68
69
		if (array_key_exists($lname, $this->data)) {
70
			return $this->data[$lname];
71
		}
72
73
		if (isset($this->unsetdata) && is_array($this->unsetdata) && array_key_exists($lname, $this->unsetdata)) {
74
			return $this->unsetdata[$lname];
75
		}
76
77
		return null;
78
	}
79
80
	/**
81
	 * PHP magic to check if an instance variable is set.
82
	 *
83
	 * @param mixed $name
84
	 */
85
	public function __isset($name) {
86
		return isset($this->data[strtolower((string) $name)]);
87
	}
88
89
	/**
90
	 * PHP magic to remove an instance variable.
91
	 *
92
	 * @param mixed $name
93
	 */
94
	public function __unset($name) {
95
		if (isset($this->{$name})) {
96
			unset($this->data[strtolower((string) $name)]);
97
			$this->changed = true;
98
		}
99
	}
100
101
	/**
102
	 * PHP magic to implement any getter, setter, has and delete operations
103
	 * on an instance variable.
104
	 * Methods like e.g. "SetVariableName($x)" and "GetVariableName()" are supported.
105
	 *
106
	 * @param mixed $name
107
	 * @param mixed $arguments
108
	 *
109
	 * @return mixed
110
	 */
111
	public function __call($name, $arguments) {
112
		$name = strtolower((string) $name);
113
		$operator = substr($name, 0, 3);
114
		$var = substr($name, 3);
115
116
		if ($name == "postunserialize") {
117
			return $this->postUnserialize();
118
		}
119
120
		if ($operator == "set" && count($arguments) == 1) {
121
			$this->{$var} = $arguments[0];
122
123
			return true;
124
		}
125
126
		if ($operator == "set" && count($arguments) == 2 && $arguments[1] === false) {
127
			$this->data[$var] = $arguments[0];
128
129
			return true;
130
		}
131
132
		// getter without argument = return variable, null if not set
133
		if ($operator == "get" && count($arguments) == 0) {
134
			return $this->{$var};
135
		}
136
137
		// getter with one argument = return variable if set, else the argument
138
		if ($operator == "get" && count($arguments) == 1) {
139
			return $this->{$var} ?? $arguments[0];
140
		}
141
142
		if ($operator == "has" && count($arguments) == 0) {
143
			return isset($this->{$var});
144
		}
145
146
		if ($operator == "del" && count($arguments) == 0) {
147
			unset($this->{$var});
148
149
			return true;
150
		}
151
152
		throw new FatalNotImplementedException(sprintf("StateObject->__call('%s'): not implemented. op: {%s} args: %d", $name, $operator, count($arguments)));
153
	}
154
155
	/**
156
	 * Called after the StateObject was unserialized.
157
	 *
158
	 * @return bool
159
	 */
160
	protected function postUnserialize() {
161
		$this->changed = false;
162
163
		return true;
164
	}
165
166
	/**
167
	 * Callback function for failed unserialize.
168
	 *
169
	 * @throws StateInvalidException
170
	 */
171
	public static function ThrowStateInvalidException(): never {
172
		throw new StateInvalidException("Unserialization failed as class was not found or not compatible");
173
	}
174
175
	/**
176
	 * JsonSerializable interface method.
177
	 *
178
	 * Serializes the object to a value that can be serialized natively by json_encode()
179
	 */
180
	public function jsonSerialize(): mixed {
181
		return [
182
			'gsSyncStateClass' => static::class,
183
			'data' => $this->data,
184
		];
185
	}
186
187
	/**
188
	 * Restores the object from a value provided by json_decode.
189
	 *
190
	 * @param $stdObj stdClass Object
191
	 */
192
	public function jsonDeserialize($stdObj) {
193
		foreach ($stdObj->data as $prop => $val) {
194
			if (is_object($val) && isset($val->gsSyncStateClass)) {
195
				$this->data[$prop] = new $val->gsSyncStateClass();
196
				$this->data[$prop]->jsonDeserialize($val);
197
				$this->data[$prop]->postUnserialize();
198
			}
199
			elseif (is_object($val)) {
200
				// json_decode converts arrays into objects, convert them back to arrays
201
				$this->data[$prop] = [];
202
				foreach ($val as $k => $v) {
203
					if (is_object($v) && isset($v->gsSyncStateClass)) {
204
						// TODO: case should be removed when removing ASDevice backwards compatibility
205
						if (strcasecmp($v->gsSyncStateClass, "ASDevice") == 0) {
206
							$this->data[$prop][$k] = new ASDevice();
207
							$this->data[$prop][$k]->Initialize(Request::GetDeviceID(), Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent());
208
						}
209
						else {
210
							$this->data[$prop][$k] = new $v->gsSyncStateClass();
211
						}
212
						$this->data[$prop][$k]->jsonDeserialize($v);
213
						$this->data[$prop][$k]->postUnserialize();
214
					}
215
					else {
216
						$this->data[$prop][$k] = $v;
217
					}
218
				}
219
			}
220
			elseif (is_array($val)) {
221
				foreach ($val as $index => $elem) {
222
					if (is_object($elem) && isset($elem->gsSyncStateClass) && !empty($elem->data)) {
223
						$this->data[$prop][$index] = new $elem->gsSyncStateClass();
224
						$this->data[$prop][$index]->jsonDeserialize($elem);
225
						$this->data[$prop][$index]->postUnserialize();
226
					}
227
					else {
228
						$this->data[$prop][$index] = $elem;
229
					}
230
				}
231
			}
232
			else {
233
				$this->data[$prop] = $val;
234
			}
235
		}
236
	}
237
}
238