Completed
Push — master ( c2cfef...1a5a57 )
by Angus
02:24
created

User_Options_Model::get_db()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3.3955

Importance

Changes 0
Metric Value
cc 3
eloc 18
nc 3
nop 2
dl 0
loc 24
ccs 11
cts 17
cp 0.6471
crap 3.3955
rs 8.9713
c 0
b 0
f 0
1
<?php declare(strict_types=1); defined('BASEPATH') or exit('No direct script access allowed');
2
3
class User_Options_Model extends CI_Model {
4
	public $options = array(
5
		/** GENERAL OPTIONS **/
6
		'category_custom_1' => array(
7
			'default' => 'disabled',
8
			'type' => 'int',
9
			'valid_options' => array(
10
				0 => 'disabled',
11
				1 => 'enabled'
12
			)
13
		),
14
		'category_custom_2' => array(
15
			'default' => 'disabled',
16
			'type' => 'int',
17
			'valid_options' => array(
18
				0 => 'disabled',
19
				1 => 'enabled'
20
			)
21
		),
22
		'category_custom_3' => array(
23
			'default' => 'disabled',
24
			'type' => 'int',
25
			'valid_options' => array(
26
				0 => 'disabled',
27
				1 => 'enabled'
28
			)
29
		),
30
		'category_custom_1_text' => array(
31
			'default' => 'Custom 1',
32
			'type' => 'string'
33
		),
34
		'category_custom_2_text' => array(
35
			'default' => 'Custom 2',
36
			'type' => 'string'
37
		),
38
		'category_custom_3_text' => array(
39
			'default' => 'Custom 3',
40
			'type' => 'string'
41
		),
42
43
		'enable_live_countdown_timer' => array(
44
			'default' => 'enabled',
45
			'type' => 'int',
46
			'valid_options' => array(
47
				0 => 'disabled',
48
				1 => 'enabled'
49
			)
50
		),
51
52
		'default_series_category' => array(
53
			'default' => 'reading',
54
			'type' => 'int',
55
			'valid_options' => array(
56
				0 => 'reading',
57
				1 => 'on-hold',
58
				2 => 'plan-to-read',
59
60
				//FIXME: (MAJOR) This should only be enabled if the custom categories are enabled
61
				// Problem is we can't easily check for this since the userscript uses it's own UserID, and not $this->User->id
62
				3 => 'custom1',
63
				4 => 'custom2',
64
				5 => 'custom3'
65
			)
66
		),
67
68
		'list_sort_type' => array(
69
			'default' => 'unread',
70
			'type' => 'int',
71
			'valid_options' => array(
72
				0 => 'unread',
73
				1 => 'alphabetical',
74
				2 => 'my_status',
75
				3 => 'latest'
76
			)
77
		),
78
79
		'list_sort_order' => array(
80
			'default' => 'asc',
81
			'type' => 'int',
82
			'valid_options' => array(
83
				0 => 'asc',
84
				1 => 'desc'
85
			)
86
		),
87
88
		'theme' => array(
89
			'default' => 'light',
90
			'type' => 'int',
91
			'valid_options' => array(
92
				0 => 'light',
93
				1 => 'dark'
94
			)
95
		),
96
97
		'enable_public_list' => array(
98
			'default' => 'disabled',
99
			'type' => 'int',
100
			'valid_options' => array(
101
				0 => 'disabled',
102
				1 => 'enabled'
103
			)
104
		),
105
106
		'mal_sync' => array(
107
			'default' => 'disabled',
108
			'type' => 'int',
109
			'valid_options' => array(
110
				0 => 'disabled',
111
				1 => 'csrf',
112
				2 => 'api'
113
			)
114
		),
115
	);
116
117 119
	public function __construct() {
118 119
		parent::__construct();
119 119
	}
120
121
	/**
122
	 * Get user option, or default option if it does not exist.
123
	 *
124
	 * @param string   $option
125
	 * @param int|null $userID
126
	 *
127
	 * @return mixed Returns option value as STRING, or FALSE if option does not exist.
128
	 */
129 119
	public function get(string $option, ?int $userID = NULL) {
130 119
		$userID = (is_null($userID) ? (int) $this->User->id : $userID);
131
132 119
		return $this->get_by_userid($option, $userID);
133
	}
134
135
	/**
136
	 * @param string     $option
137
	 * @param string|int $value
138
	 *
139
	 * @return bool
140
	 */
141
	public function set(string $option, $value) : bool {
142
		//Check if user is logged in & set ID if so
143
		if($userID = $this->User->id) {
0 ignored issues
show
Unused Code introduced by
$userID is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
144
			//Check if option is valid
145
			if(array_key_exists($option, $this->options)) {
146
				//option is valid
147
148
				$idData = array(
149
					'user_id' => $this->User->id,
150
					'name'    => $option,
151
				);
152
153
				$valueData = array();
154
				if($this->options[$option]['type'] == 'int') {
155
					$valueData['value_int'] = array_search($value, $this->options[$option]['valid_options']);
156
				} else {
157
					$valueData['value_str'] = (string) $value;
158
				}
159
160
				$success = $this->set_db($idData, $valueData);
161
				if($success) $this->session->unset_tempdata("option_{$option}");
162
			} else {
163
				$success = FALSE;
164
			}
165
		} else {
166
			$success = FALSE;
167
		}
168
		return $success;
169
	}
170
171 119
	private function get_by_userid(string $option, int $userID) {
172
		//Check if option is valid
173 119
		if(array_key_exists($option, $this->options)) {
174
			//Check if userID is > 0
175 119
			if($userID) {
176
				//Check if user has option set.
177 3
				if($row = $this->get_db($option, $userID)) {
178
					//User has option set, get proper value.
179
					if($userValue = $this->parse_value($option, $row['value_str'], $row['value_int'])) {
180
						//Value is valid. Everything is good.
181
						$value = $userValue;
182
					}
183
				}
184
			}
185
186
			//Overall fallback method.
187 119
			if(!isset($value)) $value = $this->options[$option]['default'];
188
		} else {
189
			$value = FALSE;
190
		}
191 119
		return $value;
192
	}
193
194 3
	private function get_db(string $option, int $userID) {
195
		//This function assumes we've already done some basic validation.
196
197
		//FIXME: Query duplication.
198 3
		if($this->User->id !== $userID) {
199
			$query = $this->db->select('value_str, value_int')
200
			                  ->from('user_options')
201
			                  ->where('user_id', $userID)
202
			                  ->where('name',    $option)
203
			                  ->limit(1);
204
			$data = $query->get()->row_array();
205
		} else {
206 3
			if(!($data = $this->session->tempdata("option_{$option}"))) {
207 3
				$query = $this->db->select('value_str, value_int')
208 3
				                  ->from('user_options')
209 3
				                  ->where('user_id', $userID)
210 3
				                  ->where('name',    $option)
211 3
				                  ->limit(1);
212 3
				$data = $query->get()->row_array();
213 3
				$this->session->set_tempdata("option_{$option}", $data, 3600);
214
			}
215
		}
216 3
		return $data;
217
	}
218
219
	private function set_db(array $idData, array $valueData) : bool {
220
		if($this->db->get_where('user_options', $idData)->num_rows() === 0) {
0 ignored issues
show
Documentation introduced by
$idData is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
221
			$data['type'] = (isset($a['value_int']) ? 0 : (isset($a['value_str']) == 'string' ? 1 : 2));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
Bug introduced by
The variable $a seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
222
			$success = $this->db->insert('user_options', array_merge($idData, $valueData));
223
		} else {
224
			$this->db->where($idData);
225
			$success = $this->db->update('user_options', $valueData);
226
		}
227
228
		return $success;
229
	}
230
231
	private function parse_value(string $option, $value_str, $value_int) {
232
		$type = $this->options[$option]['type'];
233
234
		switch($type) {
235
			case 'int':
236
				//TODO: How exactly should we handle INT? Just DB side?
237
				if(in_array($value_int, array_keys($this->options[$option]['valid_options']))) {
238
					$value = $this->options[$option]['valid_options'][$value_int];
239
				}
240
				break;
241
			case 'string':
242
				//TODO: We should have some basically XSS checking here?
243
				$value = (string) $value_str;
244
				break;
245
			default:
246
				//This should never happen.
247
				break;
248
		}
249
		if(!isset($value)) $value = FALSE; //FIXME: This won't play nice with BOOL type false?
250
251
		return $value;
252
	}
253
254
	//Used to quickly generate an array used with form_radio.
255
	public function generate_radio_array(string $option, string $selected_option) {
256
		if(array_key_exists($option, $this->options)) {
257
			$base_attributes = array(
258
				'name' => $option,
259
				'id'   => $option
260
			);
261
			//FIXME: Get a better solution than str_replace for removing special characters
262
			$elements = array();
263
			foreach (array_values($this->options[$option]['valid_options']) as $valid_option) {
264
				$elements[$option.'_'.str_replace(',', '_', $valid_option)] = array_merge($base_attributes, array(
265
					'value' => $valid_option
266
				));
267
			}
268
			if(isset($elements[$option.'_'.str_replace(',', '_', $selected_option)])) {
269
				$elements[$option.'_'.str_replace(',', '_', $selected_option)]['checked'] = TRUE;
270
			} else {
271
				//This should never occur, but fallbacks are always a good idea..
272
				$elements[$option.'_'.$this->options[$option]['default']]['checked'] = TRUE;
273
			}
274
			//CHECK: Should we attach this to body_data here?
275
			return $elements;
276
		} else {
277
			return FALSE;
278
		}
279
	}
280
}
281