Completed
Push — master ( 631a79...21674a )
by Angus
02:45
created

Tracker_List_Model::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 3
cts 3
cp 1
crap 1
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 Tracker_List_Model extends Tracker_Base_Model {
4 103
	public function __construct() {
5 103
		parent::__construct();
6 103
	}
7
8
	public function get(?int $userID = NULL) {
9
		$userID = (is_null($userID) ? (int) $this->User->id : $userID);
10
11
		$query = $this->db
12
			->select('tracker_chapters.*,
13
			          tracker_titles.site_id, tracker_titles.title, tracker_titles.title_url, tracker_titles.latest_chapter, tracker_titles.last_updated AS title_last_updated, tracker_titles.status AS title_status, tracker_titles.mal_id AS title_mal_id, tracker_titles.last_checked > DATE_SUB(NOW(), INTERVAL 1 WEEK) AS title_active,
14
			          tracker_sites.site, tracker_sites.site_class, tracker_sites.status AS site_status')
15
			->from('tracker_chapters')
16
			->join('tracker_titles', 'tracker_chapters.title_id = tracker_titles.id', 'left')
17
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
18
			->where('tracker_chapters.user_id', $userID)
19
			->where('tracker_chapters.active', 'Y')
20
			->get();
21
22
		$arr = ['series' => [], 'has_inactive' => FALSE];
23
		$enabledCategories = $this->getEnabledCategories($userID);
24
		foreach($enabledCategories as $category => $name) {
25
			$arr['series'][$category] = [
26
				'name'         => $name,
27
				'manga'        => [],
28
				'unread_count' => 0
29
			];
30
		}
31
		if($query->num_rows() > 0) {
32
			foreach ($query->result() as $row) {
33
				$is_unread = intval(($row->latest_chapter == $row->ignore_chapter) || ($row->latest_chapter == $row->current_chapter) ? '1' : '0');
34
				$arr['series'][$row->category]['unread_count'] = (($arr['series'][$row->category]['unread_count'] ?? 0) + !$is_unread);
35
				$data = [
36
					'id' => $row->id,
37
					'generated_current_data' => $this->sites->{$row->site_class}->getChapterData($row->title_url, $row->current_chapter),
38
					'generated_latest_data'  => $this->sites->{$row->site_class}->getChapterData($row->title_url, $row->latest_chapter),
39
40
					'generated_ignore_number' => ($row->ignore_chapter ? ' <span class=\'hidden-chapter\' title=\'The latest chapter was marked as ignored.\'>'.htmlentities($this->sites->{$row->site_class}->getChapterData($row->title_url, $row->ignore_chapter)['number']).'</span>' : ''),
41
42
					'full_title_url'        => $this->sites->{$row->site_class}->getFullTitleURL($row->title_url),
43
44
					'new_chapter_exists'    => $is_unread,
45
					'tag_list'              => $row->tags,
46
					'has_tags'              => !empty($row->tags),
47
48
					//TODO: We should have an option so chapter mal_id can take priority.
49
					'mal_id'                => $row->mal_id ?? $row->title_mal_id, //TODO: This should have an option
50
					'mal_type'              => (!is_null($row->mal_id) ? 'chapter' : 'title'),
51
52
					'title_data' => [
53
						'id'              => $row->title_id,
54
						'title'           => $row->title,
55
						'title_url'       => $row->title_url,
56
						'latest_chapter'  => $row->latest_chapter,
57
						'current_chapter' => $row->current_chapter,
58
						'last_updated'    => $row->title_last_updated,
59
						//NOTE: active is used to warn the user if a title hasn't updated (Maybe due to nobody active tracking it or other reasons).
60
						//      This will ONLY be false when an actively updating series (site enabled & title status = 0) hasn't updated within the past week.
61
						'active'          => ($row->site_status == 'disabled' || in_array($row->title_status, [/*complete*/ 1, /* one-shot */ 2, /* ignored */ 255]) || $row->title_active == 1)
62
					],
63
					'site_data' => [
64
						'id'         => $row->site_id,
65
						'site'       => $row->site,
66
						'status'     => $row->site_status
67
					]
68
				];
69
				$data['mal_icon'] = (!is_null($data['mal_id']) ? ($data['mal_id'] !== '0' ? "<a href=\"https://myanimelist.net/manga/{$data['mal_id']}\"><i class=\"sprite-site sprite-myanimelist-net\" title=\"{$data['mal_id']}\"></i></a>" : "<i class=\"sprite-site sprite-myanimelist-net-none\" title=\"none\"></i>") : '');
70
71
				$arr['series'][$row->category]['manga'][] = $data;
72
73
				if(!$arr['has_inactive']) $arr['has_inactive'] = !$data['title_data']['active'];
74
			}
75
76
			//FIXME: This is not good for speed, but we're kind of required to do this for UX purposes.
77
			//       Tablesorter has a weird sorting algorithm and has a delay before sorting which is why I'd like to avoid it.
78
			//FIXME: Is it possible to reduce duplication here without reducing speed?
79
			$sortOrder = $this->User_Options->get('list_sort_order', $userID);
80
			switch($this->User_Options->get('list_sort_type', $userID)) {
81
				case 'unread':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
82
					foreach (array_keys($arr['series']) as $category) {
83
						usort($arr['series'][$category]['manga'], function ($a, $b) use($sortOrder) {
84
							$a_text = strtolower("{$a['new_chapter_exists']} - {$a['title_data']['title']}");
85
							$b_text = strtolower("{$b['new_chapter_exists']} - {$b['title_data']['title']}");
86
87
							if($sortOrder == 'asc') {
88
								return $a_text <=> $b_text;
89
							} else {
90
								return $b_text <=> $a_text;
91
							}
92
						});
93
					}
94
					break;
95
96
				case 'alphabetical':
1 ignored issue
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
97
					foreach (array_keys($arr['series']) as $category) {
98
						usort($arr['series'][$category]['manga'], function($a, $b) use($sortOrder) {
99
							$a_text = strtolower("{$a['title_data']['title']}");
100
							$b_text = strtolower("{$b['title_data']['title']}");
101
102
							if($sortOrder == 'asc') {
103
								return $a_text <=> $b_text;
104
							} else {
105
								return $b_text <=> $a_text;
106
							}
107
						});
108
					}
109
					break;
110
111
				case 'my_status':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
112
					foreach (array_keys($arr['series']) as $category) {
113
						usort($arr['series'][$category]['manga'], function($a, $b) use($sortOrder) {
114
							$a_text = strtolower("{$a['generated_current_data']['number']}");
115
							$b_text = strtolower("{$b['generated_current_data']['number']}");
116
117
							if($sortOrder == 'asc') {
118
								return $a_text <=> $b_text;
119
							} else {
120
								return $b_text <=> $a_text;
121
							}
122
						});
123
					}
124
					break;
125
126
				case 'latest':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
127
					foreach (array_keys($arr['series']) as $category) {
128
						usort($arr['series'][$category]['manga'], function($a, $b) use($sortOrder) {
129
							$a_text = strtolower("{$a['generated_latest_data']['number']}");
130
							$b_text = strtolower("{$b['generated_latest_data']['number']}");
131
132
							if($sortOrder == 'asc') {
133
								return $a_text <=> $b_text;
134
							} else {
135
								return $b_text <=> $a_text;
136
							}
137
						});
138
					}
139
					break;
140
141
				default:
1 ignored issue
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
142
					break;
143
			}
144
		}
145
		return $arr;
146
	}
147
148
	public function update(int $userID, string $site, string $title, string $chapter, bool $active = TRUE, bool $returnTitleID = FALSE) {
149
		$success = FALSE;
150
		if($siteData = $this->Tracker->title->getSiteDataFromURL($site)) {
151
			//Validate user input
152
			if(!$this->sites->{$siteData->site_class}) {
153
				log_message('error', "{$siteData->site_class} Class doesn't exist?");
154
				return FALSE;
155
			}
156
			if(!$this->sites->{$siteData->site_class}->isValidTitleURL($title)) {
157
				//Error is already logged via isValidTitleURL
158
				return FALSE;
159
			}
160
			if(!$this->sites->{$siteData->site_class}->isValidChapter($chapter)) {
161
				//Error is already logged via isValidChapter
162
				return FALSE;
163
			}
164
165
			//NOTE: If the title doesn't exist it will be created. This maybe isn't perfect, but it works for now.
166
			$titleID = $this->Tracker->title->getID($title, (int) $siteData->id);
167
			if($titleID === 0) {
168
				//Something went wrong.
169
				log_message('error', "TitleID = 0 for {$title} @ {$siteData->id}");
170
				return FALSE;
171
			}
172
173
			$idQuery = $this->db->select('id')
174
			                    ->where('user_id', $userID)
175
			                    ->where('title_id', $titleID)
176
			                    ->get('tracker_chapters');
177
			if($idQuery->num_rows() > 0) {
178
				$success = (bool) $this->db->set(['current_chapter' => $chapter, 'active' => 'Y', 'last_updated' => NULL, 'ignore_chapter' => NULL])
179
				                           ->where('user_id', $userID)
180
				                           ->where('title_id', $titleID)
181
				                           ->update('tracker_chapters');
182
183
				if($success) {
184
					$idQueryRow = $idQuery->row();
185
					$this->History->userUpdateTitle((int) $idQueryRow->id, $chapter);
186
				}
187
			} else {
188
				$category = $this->User_Options->get('default_series_category', $userID);
189
190
				$success = (bool) $this->db->insert('tracker_chapters', [
191
					'user_id'         => $userID,
192
					'title_id'        => $titleID,
193
					'current_chapter' => $chapter,
194
					'category'        => $category,
195
					'active'          => ($active ? 'Y' : 'N')
196
				]);
197
198
				if($success) {
199
					$this->History->userAddTitle((int) $this->db->insert_id(), $chapter, $category);
0 ignored issues
show
Bug introduced by
The method insert_id() does not exist on CI_DB_query_builder. Did you maybe mean insert()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
200
				}
201
			}
202
		}
203
		return ($returnTitleID ? ($success ? ['id' => $titleID, 'chapter' => $this->sites->{$siteData->site_class}->getChapterData($title, $chapter)['number']] : $success) : $success);
0 ignored issues
show
Bug introduced by
The variable $titleID does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
204
	}
205
	public function updateByID(int $userID, int $chapterID, string $chapter) : bool {
206
		$success = (bool) $this->db->set(['current_chapter' => $chapter, 'active' => 'Y', 'last_updated' => NULL])
207
		                           ->where('user_id', $userID)
208
		                           ->where('id', $chapterID)
209
		                           ->update('tracker_chapters');
210
211
		if($success) {
212
			$this->History->userUpdateTitle($chapterID, $chapter);
213
		}
214
		return  $success;
215
	}
216
217
	public function ignoreByID(int $userID, int $chapterID, string $chapter) : bool {
218
		$success = (bool) $this->db->set(['ignore_chapter' => $chapter, 'active' => 'Y', 'last_updated' => NULL])
219
		                           ->where('user_id', $userID)
220
		                           ->where('id', $chapterID)
221
		                           ->update('tracker_chapters');
222
223
		if($success) {
224
			$this->History->userIgnoreTitle($chapterID, $chapter);
225
		}
226
		return  $success;
227
	}
228
229
	public function deleteByID(int $userID, int $chapterID) {
230
		//Series are not fully deleted, they are just marked as inactive as to hide them from the user.
231
		//This is to allow user history to function properly.
232
233
		$success = $this->db->set(['active' => 'N', 'last_updated' => NULL])
234
		                    ->where('user_id', $userID)
235
		                    ->where('id', $chapterID)
236
		                    ->update('tracker_chapters');
237
238
		return (bool) $success;
239
	}
240
	public function deleteByIDList(array $idList) : array {
241
		/*
242
		 * 0 = Success
243
		 * 1 = Invalid IDs
244
		 */
245
		$status = ['code' => 0];
246
247
		foreach($idList as $id) {
248
			if(!(ctype_digit($id) && $this->deleteByID($this->User->id, (int) $id))) {
249
				$status['code'] = 1;
250
			} else {
251
				//Delete was successful, update history too.
252
				$this->History->userRemoveTitle((int) $id);
253
			}
254
		}
255
256
		return $status;
257
	}
258
259
	public function getMalID(int $userID, int $titleID) : ?array{
260
		$malIDArr = NULL;
261
262
		//NEW METHOD
263
		//TODO: OPTION, USE BACKEND MAL ID DB WHERE POSSIBLE (DEFAULT TRUE)
264
265
		$queryC = $this->db->select('mal_id')
266
		                   ->where('user_id', $userID)
267
		                   ->where('title_id', $titleID)
268
		                   ->get('tracker_chapters');
269
270
		if($queryC->num_rows() > 0 && ($rowC = $queryC->row())) {
271
			$malIDArr = [
272
				'id'   => ($rowC->mal_id == '0' ? 'none' : $rowC->mal_id),
273
				'type' => 'chapter'
274
			];
275
		} else {
276
			$queryT = $this->db->select('mal_id')
277
			                   ->where('title_id', $titleID)
278
			                   ->get('tracker_titles');
279
280
			if($queryT->num_rows() > 0 && ($rowT = $queryT->row())) {
281
				$malIDArr = [
282
					'id'   => ($rowT->mal_id == '0' ? 'none' : $rowT->mal_id),
283
					'type' => 'title'
284
				];
285
			}
286
		}
287
288
		//OLD METHOD
289
		//TODO: Remove after a few weeks!
290
		if(is_null($malIDArr)) {
291
			$queryC2 = $this->db->select('tags')
292
			                  ->where('user_id', $userID)
293
			                  ->where('title_id', $titleID)
294
			                  ->get('tracker_chapters');
295
296
			if($queryC2->num_rows() > 0 && ($tag_string = $queryC2->row()->tags) && !is_null($tag_string)) {
297
				$arr   = preg_grep('/^mal:([0-9]+|none)$/', explode(',', $tag_string));
298
				if(!empty($arr)) {
299
					$malIDArr = [
300
						'id'   => explode(':', $arr[0])[1],
301
						'type' => 'chapter'
302
					];
303
				}
304
			}
305
		}
306
307
		return $malIDArr;
308
	}
309
	public function setMalID(int $userID, int $chapterID, ?int $malID) : bool {
310
		//TODO: Handle NULL?
311
		$success = (bool) $this->db->set(['mal_id' => $malID, 'active' => 'Y', 'last_updated' => NULL])
312
		                           ->where('user_id', $userID)
313
		                           ->where('id', $chapterID)
314
		                           ->update('tracker_chapters');
315
316
		if($success) {
317
			//MAL id update was successful, update history
318
			$this->History->userSetMalID($chapterID, $malID);
319
		}
320
		return $success;
321
	}
322
}
323