Completed
Push — master ( 8c3ec6...fb1626 )
by Angus
09:07
created

User_Options_Model::getValidOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
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
				4 => 'unread_latest'
77
			)
78
		),
79
80
		'list_sort_order' => array(
81
			'default' => 'asc',
82
			'type' => 'int',
83
			'valid_options' => array(
84
				0 => 'asc',
85
				1 => 'desc'
86
			)
87
		),
88
89
		'theme' => array(
90
			'default' => 'light',
91
			'type' => 'int',
92
			'valid_options' => array(
93
				0 => 'light',
94
				1 => 'dark'
95
			)
96
		),
97
98
		'enable_public_list' => array(
99
			'default' => 'disabled',
100
			'type' => 'int',
101
			'valid_options' => array(
102
				0 => 'disabled',
103
				1 => 'enabled'
104
			)
105
		),
106
107
		'mal_sync' => array(
108
			'default' => 'disabled',
109
			'type' => 'int',
110
			'valid_options' => array(
111
				0 => 'disabled',
112
				1 => 'csrf',
113
				2 => 'api'
114
			)
115
		),
116
	);
117
118 96
	public function __construct() {
119 96
		parent::__construct();
120
121 96
		$this->load->helper('security');
122 96
	}
123
124
	/**
125
	 * Get user option, or default option if it does not exist.
126
	 *
127
	 * @param string   $option
128
	 * @param int|null $userID
129
	 *
130
	 * @return mixed Returns option value as STRING, or FALSE if option does not exist.
131
	 */
132 96
	public function get(string $option, ?int $userID = NULL) {
133 96
		$userID = (is_null($userID) ? (int) $this->User->id : $userID);
134
135 96
		return $this->get_by_userid($option, $userID);
136
	}
137
138
	/**
139
	 * @param string     $option
140
	 * @param string|int $value
141
	 *
142
	 * @return bool
143
	 */
144
	public function set(string $option, $value) : bool {
145
		$success = FALSE;
146
		//Check if option is valid and user is logged in.
147
		if(array_key_exists($option, $this->options) && $this->ion_auth->logged_in()) {
148
			if($this->options[$option]['type'] === 'int') {
149
				$parsedValue = array_search($value, $this->options[$option]['valid_options'], TRUE);
150
			} else {
151
				$parsedValue = (string) $value;
152
			}
153
			if(!is_null($parsedValue)) {
154
				//CI doesn't have any nice way of using the query builder with JSON columns.
155
				$sql = 'UPDATE `auth_users` SET `options` = JSON_SET(COALESCE(`options`, \'{}\'), ?, ?) WHERE `id` = ?';
156
				if($success = $this->db->query($sql, ["$.{$option}", $parsedValue, $this->User->id])) {
157
					if($success) $this->session->unset_tempdata("option_{$option}");
158
				}
159
			}
160
		}
161
		return $success;
162
	}
163
164 96
	private function get_by_userid(string $option, int $userID) {
165
		//Check if option is valid
166 96
		if(array_key_exists($option, $this->options)) {
167
			//Check if userID is > 0
168 96
			if($userID) {
169
				//Check if user has option set.
170
				if($rowValue = $this->get_db($option, $userID)) {
171
					//User has option set, get proper value.
172
					if($userValue = $this->parse_value($option, $rowValue)) {
173
						//Value is valid. Everything is good.
174
						$value = $userValue;
175
					}
176
				}
177
			}
178
179
			//Overall fallback method.
180 96
			if(!isset($value)) $value = $this->options[$option]['default'];
181
		} else {
182
			$value = FALSE;
183
		}
184 96
		return $value;
185
	}
186
187
	private function get_db(string $option, int $userID) {
188
		//This function assumes we've already done some basic validation.
189
190
		//FIXME: Query duplication.
191
		if($this->User->id !== $userID) {
192
			$query = $this->db->select("options->\"$.{$option}\" AS value", FALSE)
193
			                  ->from('auth_users')
194
			                  ->where('id', $userID)
195
			                  ->limit(1);
196
			$data = $query->get()->row_object();
197
		} else {
198
			//if(!($data = $this->session->tempdata("option_{$option}"))) {
199
				$query = $this->db->select("options->\"$.{$option}\" AS value", FALSE)
200
				                  ->from('auth_users')
201
				                  ->where('id', $userID)
202
				                  ->limit(1);
203
				$data = $query->get()->row_object();
204
				$this->session->set_tempdata("option_{$option}", $data, 3600);
205
			//}
206
		}
207
		return $data->value;
208
	}
209
210
	private function parse_value(string $option, $value) {
211
		$type = $this->options[$option]['type'];
212
		switch($type) {
213
			case 'int':
214
				//TODO: How exactly should we handle INT? Just DB side?
215
				if(in_array($value, array_keys($this->options[$option]['valid_options']))) {
216
					$value = $this->options[$option]['valid_options'][$value];
217
				}
218
				break;
219
			case 'string':
220
				//TODO: We should have some basically XSS checking here?
221
				$value = htmlentities(trim(/*xss_clean(*/(string) $value/*)*/, '"'));
222
				break;
223
			default:
224
				//This should never happen.
225
				break;
226
		}
227
		if(!isset($value)) $value = FALSE; //FIXME: This won't play nice with BOOL type false?
228
		return $value;
229
	}
230
231
	//Used to quickly generate an array used with form_radio.
232
	public function generate_radio_array(string $option, string $selected_option) {
233
		if(array_key_exists($option, $this->options)) {
234
			$base_attributes = array(
235
				'name' => $option,
236
				'id'   => $option
237
			);
238
			//FIXME: Get a better solution than str_replace for removing special characters
239
			$elements = array();
240
			foreach (array_values($this->options[$option]['valid_options']) as $valid_option) {
241
				$elements[$option.'_'.str_replace(',', '_', $valid_option)] = array_merge($base_attributes, array(
242
					'value' => $valid_option
243
				));
244
			}
245
			if(isset($elements[$option.'_'.str_replace(',', '_', $selected_option)])) {
246
				$elements[$option.'_'.str_replace(',', '_', $selected_option)]['checked'] = TRUE;
247
			} else {
248
				//This should never occur, but fallbacks are always a good idea..
249
				$elements[$option.'_'.$this->options[$option]['default']]['checked'] = TRUE;
250
			}
251
			//CHECK: Should we attach this to body_data here?
252
			return $elements;
253
		} else {
254
			return FALSE;
255
		}
256
	}
257
258
	public function getValidOptions(string $option) : array {
259
		return $this->options[$option]['valid_options'];
260
	}
261
}
262