Completed
Push — master ( 9553e2...91bcf6 )
by Angus
09:37
created

Tracker_Model::favouriteChapter()   C

Complexity

Conditions 9
Paths 9

Size

Total Lines 75
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
eloc 47
nc 9
nop 4
dl 0
loc 75
ccs 0
cts 0
cp 0
crap 90
rs 5.875
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
class Tracker_Model extends CI_Model {
4
	public $sites;
5
	public $enabledCategories;
6
7 94
	public function __construct() {
8 94
		parent::__construct();
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CI_Model as the method __construct() does only exist in the following sub-classes of CI_Model: Auth_Model, Batoto, DynastyScans, GameOfScanlation, History_Model, KireiCake, KissManga, MangaFox, MangaHere, MangaPanda, MangaStream, Site_Model, Sites_Model, Tracker_Model, User_Model, User_Options_Model, WebToons. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
9
10 94
		$this->load->database();
11
12 94
		$this->enabledCategories = [
13
			'reading'      => 'Reading',
14
			'on-hold'      => 'On-Hold',
15
			'plan-to-read' => 'Plan to Read'
16
		];
17 94
		if($this->User_Options->get('category_custom_1') == 'enabled') {
18
			$this->enabledCategories['custom1'] = $this->User_Options->get('category_custom_1_text');
19
		}
20 94
		if($this->User_Options->get('category_custom_2') == 'enabled') {
21
			$this->enabledCategories['custom2'] = $this->User_Options->get('category_custom_2_text');
22
		}
23 94
		if($this->User_Options->get('category_custom_3') == 'enabled') {
24
			$this->enabledCategories['custom3'] = $this->User_Options->get('category_custom_3_text');
25
		}
26
27 94
		require_once(APPPATH.'models/Site_Model.php');
28 94
		$this->sites = new Sites_Model;
29 94
	}
30
31
	/****** GET TRACKER *******/
32
	public function get_tracker_from_user_id(int $userID) {
33
		$query = $this->db
34
			->select('tracker_chapters.*,
35
			          tracker_titles.site_id, tracker_titles.title, tracker_titles.title_url, tracker_titles.latest_chapter, tracker_titles.last_updated AS title_last_updated,
36
			          tracker_sites.site, tracker_sites.site_class, tracker_sites.status AS site_status')
37
			->from('tracker_chapters')
38
			->join('tracker_titles', 'tracker_chapters.title_id = tracker_titles.id', 'left')
39
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
40
			->where('tracker_chapters.user_id', $userID)
41
			->where('tracker_chapters.active', 'Y')
42
			->get();
43
44
		$arr = [];
45
		foreach($this->enabledCategories as $category => $name) {
46
			$arr[$category] = [
47
				'name'         => $name,
48
				'manga'        => [],
49
				'unread_count' => 0
50
			];
51
		}
52
		if($query->num_rows() > 0) {
53
			foreach ($query->result() as $row) {
54
				$is_unread     = intval($row->latest_chapter == $row->current_chapter ? '1' : '0');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 27 spaces but found 5 spaces

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
55
				$arr[$row->category]['unread_count'] = (($arr[$row->category]['unread_count'] ?? 0) + !$is_unread);
56
				$arr[$row->category]['manga'][] = [
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
57
					'id' => $row->id,
58
					'generated_current_data' => $this->sites->{$row->site_class}->getChapterData($row->title_url, $row->current_chapter),
59
					'generated_latest_data'  => $this->sites->{$row->site_class}->getChapterData($row->title_url, $row->latest_chapter),
60
					'full_title_url'        =>  $this->sites->{$row->site_class}->getFullTitleURL($row->title_url),
61
62
					'new_chapter_exists'    => $is_unread,
63
					'tag_list'              => $row->tags,
64
					'has_tags'              => !empty($row->tags),
65
66
					'title_data' => [
67
						'id'              => $row->title_id,
68
						'title'           => $row->title,
69
						'title_url'       => $row->title_url,
70
						'latest_chapter'  => $row->latest_chapter,
71
						'current_chapter' => $row->current_chapter,
72
						'last_updated'    => $row->title_last_updated
73
					],
74
					'site_data' => [
75
						'id'         => $row->site_id,
76
						'site'       => $row->site,
77
						'status'     => $row->site_status
78
					]
79
				];
80
			}
81
82
			//NOTE: This does not sort in the same way as tablesorter, but it works better.
83
			foreach (array_keys($arr) as $category) {
84
				usort($arr[$category]['manga'], function ($a, $b) {
85
					return strtolower("{$a['new_chapter_exists']} - {$a['title_data']['title']}") <=> strtolower("{$b['new_chapter_exists']} - {$b['title_data']['title']}");
86
				});
87
			}
88
		}
89
		return $arr;
90
	}
91
92
	public function getSiteDataFromURL(string $site_url) {
93
		$query = $this->db->select('id, site_class')
94
		                  ->from('tracker_sites')
95
		                  ->where('site', $site_url)
96
		                  ->get();
97
98
		if($query->num_rows() > 0) {
99
			$siteData = $query->row();
100
		}
101
102
		return $siteData ?? FALSE;
103
	}
104
105
	public function getTitleID(string $titleURL, int $siteID) {
106
		$query = $this->db->select('id')
107
		                  ->from('tracker_titles')
108
		                  ->where('title_url', $titleURL)
109
		                  ->where('site_id', $siteID)
110
		                  ->get();
111
112
		if($query->num_rows() > 0) {
113
			$titleID = $query->row('id');
114
		} else {
115
			//TODO: Check if title is valid URL!
116
			$titleID = $this->addTitle($titleURL, $siteID);
117
		}
118
119
		return $titleID;
120
	}
121
122
	public function updateTracker(int $userID, string $site, string $title, string $chapter) : bool {
123
		$success = FALSE;
124
		if($siteData = $this->Tracker->getSiteDataFromURL($site)) {
125
			//Validate user input
126
			if(!$this->sites->{$siteData->site_class}->isValidTitleURL($title)) {
127
				//Error is already logged via isValidTitleURL
128
				return FALSE;
129
			}
130
			if(!$this->sites->{$siteData->site_class}->isValidChapter($chapter)) {
131
				//Error is already logged via isValidChapter
132
				return FALSE;
133
			}
134
135
			//NOTE: If the title doesn't exist it will be created. This maybe isn't perfect, but it works for now.
136
			$titleID = $this->Tracker->getTitleID($title, (int) $siteData->id);
137
			if($titleID === 0) {
138
				//Something went wrong.
139
				log_message('error', "TitleID = 0 for {$title} @ {$siteData->id}");
140
				return FALSE;
141
			}
142
143
			$idQuery = $this->db->select('id')
144
			                    ->where('user_id', $userID)
145
			                    ->where('title_id', $titleID)
146
			                    ->get('tracker_chapters');
147
			if($idQuery->num_rows() > 0) {
148
				$success = (bool) $this->db->set(['current_chapter' => $chapter, 'active' => 'Y', 'last_updated' => NULL])
149
				                    ->where('user_id', $userID)
150
				                    ->where('title_id', $titleID)
151
				                    ->update('tracker_chapters');
152
153
				if($success) {
154
					$idQueryRow = $idQuery->row();
155
					$this->History->userUpdateTitle((int) $idQueryRow->id, $chapter);
156
				}
157
			} else {
158
				$category = $this->User_Options->get_by_userid('default_series_category', $userID);
159
				$success = (bool) $this->db->insert('tracker_chapters', [
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
160
					'user_id'         => $userID,
161
					'title_id'        => $titleID,
162
					'current_chapter' => $chapter,
163
					'category'        => $category
164
				]);
165
166
				if($success) {
167
					$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...
168
				}
169
			}
170
		}
171
		return $success;
172
	}
173
174
	public function updateTrackerByID(int $userID, int $chapterID, string $chapter) : bool {
175
		$success = (bool) $this->db->set(['current_chapter' => $chapter, 'active' => 'Y', 'last_updated' => NULL])
176
		                    ->where('user_id', $userID)
177
		                    ->where('id', $chapterID)
178
		                    ->update('tracker_chapters');
179
180
		if($success) {
181
			$this->History->userUpdateTitle($chapterID, $chapter);
182
		}
183
		return  $success;
184
	}
185
186 View Code Duplication
	public function deleteTrackerByID(int $userID, int $chapterID) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
		//Series are not fully deleted, they are just marked as inactive as to hide them from the user.
188
		//This is to allow user history to function properly.
189
190
		$success = $this->db->set(['active' => 'N', 'last_updated' => NULL])
191
		                    ->where('user_id', $userID)
192
		                    ->where('id', $chapterID)
193
		                    ->update('tracker_chapters');
194
195
		return (bool) $success;
196
	}
197
	private function updateTitleById(int $id, string $latestChapter) {
198
		//FIXME: Really not too happy with how we're doing history stuff here, it just feels messy.
199
		$query = $this->db->select('latest_chapter AS current_chapter')
200
		                  ->from('tracker_titles')
201
		                  ->where('id', $id)
202
		                  ->get();
203
		$row = $query->row();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
204
205
		$success = $this->db->set(['latest_chapter' => $latestChapter]) //last_updated gets updated via a trigger if something changes
206
		                    ->where('id', $id)
207
		                    ->update('tracker_titles');
208
209
		//Update History
210
		//NOTE: To avoid doing another query to grab the last_updated time, we just use time() which <should> get the same thing.
211
		//FIXME: The <preferable> solution here is we'd just check against the last_updated time, but that can have a few issues.
212
		$this->History->updateTitleHistory($id, $row->current_chapter, $latestChapter, date('Y-m-d H:i:s'));
213
214
		return (bool) $success;
215
	}
216
	private function updateTitleDataById(int $id, array $titleData) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
217
		$success = $this->db->set($titleData)
218
		                    ->where('id', $id)
219
		                    ->update('tracker_titles');
220
221
		return (bool) $success;
222
	}
223
	private function addTitle(string $titleURL, int $siteID) {
224
		$query = $this->db->select('site, site_class')
225
		                  ->from('tracker_sites')
226
		                  ->where('id', $siteID)
227
		                  ->get();
228
229
		$titleData = $this->sites->{$query->row()->site_class}->getTitleData($titleURL);
230
		$this->db->insert('tracker_titles', array_merge($titleData, ['title_url' => $titleURL, 'site_id' => $siteID]));
231
		$titleID = $this->db->insert_id();
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...
232
233
		$this->History->updateTitleHistory((int) $titleID, NULL, $titleData['latest_chapter'], $titleData['last_updated']);
234
		return $titleID;
235
	}
236
237
	/**
238
	 * Checks for any titles that haven't updated in 16 hours and updates them.
239
	 * This is ran every 6 hours via a cron job.
240
	 */
241
	public function updateLatestChapters() {
242
		$query = $this->db->select('
243
				tracker_titles.id,
244
				tracker_titles.title,
245
				tracker_titles.title_url,
246
				tracker_sites.site,
247
				tracker_sites.site_class,
248
				tracker_sites.status,
249
				tracker_titles.latest_chapter,
250
				tracker_titles.last_updated,
251
				from_unixtime(MAX(auth_users.last_login)) AS timestamp
252
			')
253
			->from('tracker_titles')
254
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
255
			->join('tracker_chapters', 'tracker_titles.id = tracker_chapters.title_id', 'left')
256
			->join('auth_users', 'tracker_chapters.user_id = auth_users.id', 'left')
257
			->where('tracker_sites.status', 'enabled')
258
			->where('tracker_chapters.active', 'Y') //CHECK: Does this apply BEFORE the GROUP BY/HAVING is done?
259
			->where('(`complete` = "N" AND (`latest_chapter` = NULL OR `last_checked` < DATE_SUB(NOW(), INTERVAL 12 HOUR)))', NULL, FALSE) //TODO: Each title should have specific interval time?
260
			->or_where('(`complete` = "Y" AND `last_checked` < DATE_SUB(NOW(), INTERVAL 1 WEEK))', NULL, FALSE)
261
			->group_by('tracker_titles.id')
262
			->having('timestamp IS NOT NULL')
263
			->having('timestamp > DATE_SUB(NOW(), INTERVAL 120 HOUR)')
264
			->order_by('tracker_titles.title', 'ASC')
265
			->get();
266
267
		if($query->num_rows() > 0) {
268
			foreach ($query->result() as $row) {
269
				print "> {$row->title} <{$row->site_class}>"; //Print this prior to doing anything so we can more easily find out if something went wrong
270
				$titleData = $this->sites->{$row->site_class}->getTitleData($row->title_url);
271
				if(!is_null($titleData['latest_chapter'])) {
272
					//FIXME: "At the moment" we don't seem to be doing anything with TitleData['last_updated'].
273
					//       Should we even use this? Y/N
274
					if($this->updateTitleById((int) $row->id, $titleData['latest_chapter'])) {
275
						//Make sure last_checked is always updated on successful run.
276
						//CHECK: Is there a reason we aren't just doing this in updateTitleById?
277
						$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
278
						         ->where('id', $row->id)
279
						         ->update('tracker_titles');
280
281
						print " - ({$titleData['latest_chapter']})\n";
282
					}
283
				} else {
284
					log_message('error', "{$row->title} failed to update successfully");
285
					print " - FAILED TO PARSE\n";
286
				}
287
			}
288
		}
289
	}
290
291
	public function exportTrackerFromUserID(int $userID) {
292
		$query = $this->db
293
			->select('tracker_chapters.current_chapter,
294
			          tracker_chapters.category,
295
			          tracker_titles.title_url,
296
			          tracker_sites.site')
297
			->from('tracker_chapters')
298
			->join('tracker_titles', 'tracker_chapters.title_id = tracker_titles.`id', 'left')
299
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
300
			->where('tracker_chapters.user_id', $userID)
301
			->where('tracker_chapters.active', 'Y')
302
			->get();
303
304
		$arr = [];
305
		if($query->num_rows() > 0) {
306
			foreach ($query->result() as $row) {
307
				$arr[$row->category][] = [
308
					'site'            => $row->site,
309
					'title_url'       => $row->title_url,
310
					'current_chapter' => $row->current_chapter
311
				];
312
			}
313
314
			return $arr;
315
		}
316
	}
317
318
	public function importTrackerFromJSON(string $json_string) : array {
319
		//We already know the this is a valid JSON string as it was validated by form_validator.
320
		$json = json_decode($json_string, TRUE);
321
322
		/*
323
		 * 0 = Success
324
		 * 1 = Invalid keys.
325
		 * 2 = Has failed rows
326
		 */
327
		$status = ['code' => 0, 'failed_rows' => []];
328
329
		$categories = array_keys($json);
330
		if(count($categories) === array_intersect(['reading', 'on-hold', 'plan-to-read', 'custom1', 'custom2', 'custom3'], $categories)) {
331
			$json_keys = array_keys(call_user_func_array('array_merge', $json));
332
333
			if(count($json_keys) === 3 && !array_diff(array('site', 'title_url', 'current_chapter'), $json_keys)) {
334
				foreach($categories as $category) {
335
					foreach($json[$category] as $row) {
336
						$success = $this->updateTracker($this->User->id, $row['site'], $row['title_url'], $row['current_chapter']);
337
						if(!$success) {
338
							$status['code']          = 2;
339
							$status['failed_rows'][] = $row;
340
						}
341
					}
342
				}
343
			} else {
344
				$status['code'] = 1;
345
			}
346
		} else {
347
			$status['code'] = 1;
348
		}
349
		return $status;
350
	}
351
352
	public function deleteTrackerByIDList(array $idList) : array {
353
		/*
354
		 * 0 = Success
355
		 * 1 = Invalid IDs
356
		 */
357
		$status = ['code' => 0];
358
359 View Code Duplication
		foreach($idList as $id) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360
			if(!(ctype_digit($id) && $this->deleteTrackerByID($this->User->id, (int) $id))) {
361
				$status['code'] = 1;
362
			} else {
363
				//Delete was successful, update history too.
364
				$this->History->userRemoveTitle((int) $id);
365
			}
366
		}
367
368
		return $status;
369
	}
370
371
	public function setCategoryByIDList(array $idList, string $category) : array {
372
		/*
373
		 * 0 = Success
374
		 * 1 = Invalid IDs
375
		 * 2 = Invalid category
376
		 */
377
		$status = ['code' => 0];
378
379
		if(in_array($category, array_keys($this->enabledCategories))) {
380 View Code Duplication
			foreach($idList as $id) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
381
				if(!(ctype_digit($id) && $this->setCategoryTrackerByID($this->User->id, (int) $id, $category))) {
382
					$status['code'] = 1;
383
				} else {
384
					//Category update was successful, update history too.
385
					$this->History->userUpdateCategory((int) $id, $category);
386
				}
387
			}
388
		} else {
389
			$status['code'] = 2;
390
		}
391
392
		return $status;
393
	}
394 View Code Duplication
	public function setCategoryTrackerByID(int $userID, int $chapterID, string $category) : bool {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
395
		$success = $this->db->set(['category' => $category, 'active' => 'Y', 'last_updated' => NULL])
396
		                    ->where('user_id', $userID)
397
		                    ->where('id', $chapterID)
398
		                    ->update('tracker_chapters');
399
400
		return (bool) $success;
401
	}
402
403
404
	public function updateTagsByID(int $userID, int $chapterID, string $tag_string) : bool {
405
		$success = FALSE;
406
		if(preg_match("/^[a-z0-9-_,]{0,255}$/", $tag_string)) {
407
			$success = (bool) $this->db->set(['tags' => $tag_string, 'active' => 'Y', 'last_updated' => NULL])
408
			                           ->where('user_id', $userID)
409
			                           ->where('id', $chapterID)
410
			                           ->update('tracker_chapters');
411
		}
412
413
		if($success) {
414
			//Tag update was successful, update history
415
			$this->History->userUpdateTags($chapterID, $tag_string);
416
		}
417
		return $success;
418
	}
419
420
	public function favouriteChapter(int $userID, string $site, string $title, string $chapter) : array {
421
		$success = array(
422
			'status' => 'Something went wrong',
423
			'bool'   => FALSE
424
		);
425
		if($siteData = $this->Tracker->getSiteDataFromURL($site)) {
426
			//Validate user input
427
			if(!$this->sites->{$siteData->site_class}->isValidTitleURL($title)) {
428
				//Error is already logged via isValidTitleURL
429
				$success['status'] = 'Title URL is not valid';
430
				return $success;
431
			}
432
			if(!$this->sites->{$siteData->site_class}->isValidChapter($chapter)) {
433
				//Error is already logged via isValidChapter
434
				$success['status'] = 'Chapter URL is not valid';
435
				return $success;
436
			}
437
438
			//NOTE: If the title doesn't exist it will be created. This maybe isn't perfect, but it works for now.
439
			$titleID = $this->Tracker->getTitleID($title, (int) $siteData->id);
440
			if($titleID === 0) {
441
				//Something went wrong.
442
				log_message('error', "TitleID = 0 for {$title} @ {$siteData->id}");
443
				return $success;
444
			}
445
446
			//We need the series to be tracked
447
			$idCQuery = $this->db->select('id')
448
			                    ->where('user_id', $userID)
449
			                    ->where('title_id', $titleID)
450
			                    ->get('tracker_chapters');
451
			if($idCQuery->num_rows() > 0) {
452
				$idCQueryRow = $idCQuery->row();
453
454
				//Check if it is already favourited
455
				$idFQuery = $this->db->select('id')
456
				                    ->where('chapter_id', $idCQueryRow->id)
457
				                    ->where('chapter', $chapter)
458
				                    ->get('tracker_favourites');
459
				if($idFQuery->num_rows() > 0) {
460
					//Chapter is already favourited, so remove it from DB
461
					$idFQueryRow = $idFQuery->row();
462
463
					$isSuccess = (bool) $this->db->where('id', $idFQueryRow->id)
464
					                           ->delete('tracker_favourites');
465
466
					if($isSuccess) {
467
						$success = array(
468
							'status' => 'Unfavourited',
469
							'bool'   => TRUE
470
						);
471
						$this->History->userRemoveFavourite((int) $idCQueryRow->id, $chapter);
472
					}
473
				} else {
474
					//Chapter is not favourited, so add to DB.
475
					$isSuccess = (bool) $this->db->insert('tracker_favourites', [
476
						'chapter_id'      => $idCQueryRow->id,
477
						'chapter'         => $chapter,
478
						'updated_at'      => date('Y-m-d H:i:s')
479
					]);
480
481
					if($isSuccess) {
482
						$success = array(
483
							'status' => 'Favourited',
484
							'bool'   => TRUE
485
						);
486
						$this->History->userAddFavourite((int) $idCQueryRow->id, $chapter);
487
					}
488
				}
489
			} else {
490
				$success['status'] = 'Series needs to be tracked before we can favourite chapters';
491
			}
492
		}
493
		return $success;
494
	}
495
	public function getFavourites(int $page) : array {
496
		$rowsPerPage = 50;
497
		$query = $this->db
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
498
			->select('SQL_CALC_FOUND_ROWS
499
			          tt.title, tt.title_url,
500
			          ts.site, ts.site_class,
501
			          tf.chapter, tf.updated_at', FALSE)
502
			->from('tracker_favourites AS tf')
503
			->join('tracker_chapters AS tc', 'tf.chapter_id = tc.id', 'left')
504
			->join('tracker_titles AS tt', 'tc.title_id = tt.id', 'left')
505
			->join('tracker_sites AS ts', 'tt.site_id = ts.id', 'left')
506
			->where('tc.user_id', $this->User->id) //CHECK: Is this inefficient? Would it be better to have a user_id column in tracker_favourites?
507
			->order_by('tf.id DESC')
508
			->limit($rowsPerPage, ($rowsPerPage * ($page - 1)))
509
			->get();
510
511
		$arr = ['rows' => [], 'totalCount' => 0];
512
		if($query->num_rows() > 0) {
513
			foreach($query->result() as $row) {
514
				$arrRow = [];
515
516
				$arrRow['updated_at'] = $row->updated_at;
517
				$arrRow['title']      = $row->title;
518
				$arrRow['title_url']  = $this->Tracker->sites->{$row->site_class}->getFullTitleURL($row->title_url);
519
520
				$arrRow['site'] = $row->site;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
521
				$arrRow['site_sprite'] = str_replace('.', '-', $row->site);
522
523
				$chapterData = $this->Tracker->sites->{$row->site_class}->getChapterData($row->title_url, $row->chapter);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
524
				$arrRow['chapter'] = "<a href=\"{$chapterData['url']}\">{$chapterData['number']}</a>";
525
				$arr['rows'][] = $arrRow;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
526
			}
527
			$arr['totalPages'] = ceil($this->db->query('SELECT FOUND_ROWS() count;')->row()->count / $rowsPerPage);
528
		}
529
		return $arr;
530
531
	}
532
533
	public function getUsedCategories(int $userID) : array {
0 ignored issues
show
Unused Code introduced by
The parameter $userID is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
534
		$usedCategories = [];
0 ignored issues
show
Unused Code introduced by
$usedCategories 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...
535
536
		$query = $this->db->distinct()
537
		                  ->select('category')
538
		                  ->from('tracker_chapters')
539
		                  ->where('tracker_chapters.active', 'Y')
540
		                  ->get();
541
542
		return array_column($query->result_array(), 'category');
543
	}
544
545
	//FIXME: Should this be moved elsewhere??
546
	public function getNextUpdateTime() : string {
547
		$temp_now = new DateTime();
548
		$temp_now->setTimezone(new DateTimeZone('America/New_York'));
549
		$temp_now_formatted = $temp_now->format('Y-m-d H:i:s');
550
551
		//NOTE: PHP Bug: DateTime:diff doesn't play nice with setTimezone, so we need to create another DT object
552
		$now         = new DateTime($temp_now_formatted);
553
		$future_date = new DateTime($temp_now_formatted);
554
		$now_hour    = (int) $now->format('H');
555
		if($now_hour < 4) {
556
			//Time until 4am
557
			$future_date->setTime(4, 00);
558
		} elseif($now_hour < 8) {
559
			//Time until 8am
560
			$future_date->setTime(8, 00);
561
		} elseif($now_hour < 12) {
562
			//Time until 12pm
563
			$future_date->setTime(12, 00);
564
		} elseif($now_hour < 16) {
565
			//Time until 4pm
566
			$future_date->setTime(16, 00);
567
		} elseif($now_hour < 20) {
568
			//Time until 8pm
569
			$future_date->setTime(20, 00);
570
		} else {
571
			//Time until 12am
572
			$future_date->setTime(00, 00);
573
			$future_date->add(new DateInterval('P1D'));
574
		}
575
576
		$interval = $future_date->diff($now);
577
		return $interval->format("%H:%I:%S");
578
	}
579
580
	public function reportBug(string $text, $userID = NULL, $url = NULL) : bool {
581
		$this->load->library('email');
582
583
		//This is pretty barebones bug reporting, and honestly not a great way to do it, but it works for now (until the Github is public).
584
		$body = "".
585
		(!is_null($url) && !empty($url) ? "URL: ".htmlspecialchars(substr($url, 0, 255))."<br>\n" : "").
586
		"Submitted by: ".$this->input->ip_address().(!is_null($userID) ? "| {$userID}" : "")."<br>\n".
587
		"<br>Bug report: ".htmlspecialchars(substr($text, 0, 1000));
588
589
		$success = TRUE;
590
		$this->email->from('[email protected]', $this->config->item('site_title', 'ion_auth'));
591
		$this->email->to($this->config->item('admin_email', 'ion_auth'));
592
		$this->email->subject($this->config->item('site_title', 'ion_auth')." - Bug Report");
593
		$this->email->message($body);
594
		if(!$this->email->send()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->email->send() of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
595
			$success = FALSE;
596
		}
597
		return $success;
598
	}
599
600
	/*************************************************/
601
	public function sites() {
602
		return $this;
603
	}
604
}
605