Completed
Push — master ( 592cc9...b664a6 )
by Angus
02:30
created

Base_Site_Model   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 31.9%

Importance

Changes 0
Metric Value
dl 0
loc 383
ccs 37
cts 116
cp 0.319
rs 8.6206
c 0
b 0
f 0
wmc 50
lcom 1
cbo 2

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
getFullTitleURL() 0 1 ?
getChapterData() 0 1 ?
getTitleData() 0 1 ?
A isValidTitleURL() 0 5 2
A isValidChapter() 0 5 2
C parseTitleDataDOM() 0 55 12
A cleanTitleDataDOM() 0 3 1
C doCustomFollow() 0 27 7
A handleCustomFollow() 0 1 1
A doCustomUpdate() 0 1 1
A doCustomCheck() 0 1 1
C doCustomCheckCompare() 0 50 16
B get_content() 0 44 6

How to fix   Complexity   

Complex Class

Complex classes like Base_Site_Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Base_Site_Model, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
/**
4
 * Class Tracker_Sites_Model
5
 */
6
class Tracker_Sites_Model extends CI_Model {
7 119
	public function __construct() {
8 119
		parent::__construct();
9 119
	}
10
11
	public function __get($name) {
12
		//TODO: Is this a good idea? There wasn't a good consensus on if this is good practice or not..
13
		//      It's probably a minor speed reduction, but that isn't much of an issue.
14
		//      An alternate solution would simply have a function which generates a PHP file with code to load each model. Similar to: https://github.com/shish/shimmie2/blob/834bc740a4eeef751f546979e6400fd089db64f8/core/util.inc.php#L1422
15
		if(!class_exists($name) || !(in_array(get_parent_class($name), ['Base_Site_Model', 'Base_FoolSlide_Site_Model']))) {
16
			return get_instance()->{$name};
17
		} else {
18
			$this->loadSite($name);
19
			return $this->{$name};
20
		}
21
	}
22
23
	private function loadSite(string $siteName) : void {
24
		$this->{$siteName} = new $siteName();
25
	}
26
}
27
28
abstract class Base_Site_Model extends CI_Model {
29
	public $site          = '';
30
	public $titleFormat   = '//';
31
	public $chapterFormat = '//';
32
33
	/**
34
	 * 0: No custom updater.
35
	 * 1: Uses following page.
36
	 * 2: Uses latest releases page.
37
	 */
38
	public $customType = 0;
39
40 16
	public function __construct() {
41 16
		parent::__construct();
42
43 16
		$this->load->database();
44
45 16
		$this->site = get_class($this);
46 16
	}
47
48
	/**
49
	 * Generates URL to the title page of the requested series.
50
	 *
51
	 * NOTE: In some cases, we are required to store more data in the title_string than is needed to generate the URL. (Namely as the title_string is our unique identifier for that series)
52
	 *       When storing additional data, we use ':--:' as a delimiter to separate the data. Make sure to handle this as needed.
53
	 *
54
	 * Example:
55
	 *    return "http://mangafox.me/manga/{$title_url}/";
56
	 *
57
	 * Example (with extra data):
58
	 *    $title_parts = explode(':--:', title_url);
59
	 *    return "https://bato.to/comic/_/comics/-r".$title_parts[0];
60
	 *
61
	 * @param string $title_url
62
	 * @return string
63
	 */
64
	abstract public function getFullTitleURL(string $title_url) : string;
65
66
	/**
67
	 * Generates chapter data from given $title_url and $chapter.
68
	 *
69
	 * Chapter must be in a (v[0-9]+/)?c[0-9]+(\..+)? format.
70
	 *
71
	 * NOTE: In some cases, we are required to store the chapter number, and the segment required to generate the chapter URL separately.
72
	 *       Much like when generating the title URL, we use ':--:' as a delimiter to separate the data. Make sure to handle this as needed.
73
	 *
74
	 * Example:
75
	 *     return [
76
	 *        'url'    => $this->getFullTitleURL($title_url).'/'.$chapter,
77
	 *        'number' => "c{$chapter}"
78
	 *    ];
79
	 *
80
	 * @param string $title_url
81
	 * @param string $chapter
82
	 * @return array [url, number]
83
	 */
84
	abstract public function getChapterData(string $title_url, string $chapter) : array;
85
86
	/**
87
	 * Used to get the latest chapter of given $title_url.
88
	 *
89
	 * This <should> utilize both get_content and parseTitleDataDOM functions when possible, as these can both reduce a lot of the code required to set this up.
90
	 *
91
	 * $titleData params must be set accordingly:
92
	 * * `title` should always be used with html_entity_decode.
93
	 * * `latest_chapter` must match $this->chapterFormat.
94
	 * * `last_updated` should always be in date("Y-m-d H:i:s") format.
95
	 * * `followed` should never be set within via getTitleData, with the exception of via a array_merge with doCustomFollow.
96
	 *
97
	 * $firstGet is set to true when the series is first added to the DB, and is used to follow the series on given site (if possible).
98
	 *
99
	 * @param string $title_url
100
	 * @param bool   $firstGet
101
	 * @return array|null [title,latest_chapter,last_updated,followed?]
102
	 */
103
	abstract public function getTitleData(string $title_url, bool $firstGet = FALSE) : ?array;
104
105
	/**
106
	 * Validates given $title_url against titleFormat.
107
	 *
108
	 * Failure to match against titleFormat will stop the series from being added to the DB.
109
	 *
110
	 * @param string $title_url
111
	 * @return bool
112
	 */
113 2
	final public function isValidTitleURL(string $title_url) : bool {
114 2
		$success = (bool) preg_match($this->titleFormat, $title_url);
115 2
		if(!$success) log_message('error', "Invalid Title URL ({$this->site}): {$title_url}");
116 2
		return $success;
117
	}
118
119
	/**
120
	 * Validates given $chapter against chapterFormat.
121
	 *
122
	 * Failure to match against chapterFormat will stop the chapter being updated.
123
	 *
124
	 * @param string $chapter
125
	 * @return bool
126
	 */
127 2
	final public function isValidChapter(string $chapter) : bool {
128 2
		$success = (bool) preg_match($this->chapterFormat, $chapter);
129 2
		if(!$success) log_message('error', "Invalid Chapter ({$this->site}): {$chapter}");
130 2
		return $success;
131
	}
132
133
	/**
134
	 * Used by getTitleData (& similar functions) to get the requested page data.
135
	 *
136
	 * @param string $url
137
	 * @param string $cookie_string
138
	 * @param string $cookiejar_path
139
	 * @param bool   $follow_redirect
140
	 * @param bool   $isPost
141
	 * @param array  $postFields
142
	 *
143
	 * @return array|bool
144
	 */
145
	final protected function get_content(string $url, string $cookie_string = "", string $cookiejar_path = "", bool $follow_redirect = FALSE, bool $isPost = FALSE, array $postFields = []) {
146
		$ch = curl_init();
147
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
148
		curl_setopt($ch, CURLOPT_ENCODING , "gzip");
149
		//curl_setopt($ch, CURLOPT_VERBOSE, 1);
150
		curl_setopt($ch, CURLOPT_HEADER, 1);
151
152
		if($follow_redirect)        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
153
154
		if(!empty($cookie_string))  curl_setopt($ch, CURLOPT_COOKIE, $cookie_string);
155
		if(!empty($cookiejar_path)) curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiejar_path);
156
157
		//Some sites check the useragent for stuff, use a pre-defined user-agent to avoid stuff.
158
		curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.0 Safari/537.36');
159
160
		//NOTE: This is required for SSL URLs for now. Without it we tend to get error code 60.
161
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); //FIXME: This isn't safe, but it allows us to grab SSL URLs
162
163
		curl_setopt($ch, CURLOPT_URL, $url);
164
165
		if($isPost) {
166
			curl_setopt($ch,CURLOPT_POST, count($postFields));
167
			curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($postFields));
168
		}
169
170
		$response = curl_exec($ch);
171
		if($response === FALSE) {
172
			log_message('error', "curl failed with error: ".curl_errno($ch)." | ".curl_error($ch));
173
			//FIXME: We don't always account for FALSE return
174
			return FALSE;
175
		}
176
177
		$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
178
		$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
179
		$headers     = http_parse_headers(substr($response, 0, $header_size));
180
		$body        = substr($response, $header_size);
181
		curl_close($ch);
182
183
		return [
184
			'headers'     => $headers,
185
			'status_code' => $status_code,
186
			'body'        => $body
187
		];
188
	}
189
190
	/**
191
	 * Used by getTitleData to get the title, latest_chapter & last_updated data from the data returned by get_content.
192
	 *
193
	 * parseTitleDataDOM checks if the data returned by get_content is valid via a few simple checks.
194
	 * * If the request was actually successful, had a valid status code & data wasn't empty. We also do an additional check on an optional $failure_string param, which will throw a failure if it's matched.
195
	 *
196
	 * Data is cleaned by cleanTitleDataDOM prior to being passed to DOMDocument.
197
	 *
198
	 * All $node_* params must be XPath to the requested node, and must only return 1 result. Anything else will throw a failure.
199
	 *
200
	 * @param array  $content
201
	 * @param string $title_url
202
	 * @param string $node_title_string
203
	 * @param string $node_row_string
204
	 * @param string $node_latest_string
205
	 * @param string $node_chapter_string
206
	 * @param string $failure_string
207
	 * @return DOMElement[]|false [nodes_title,nodes_chapter,nodes_latest]
208
	 */
209
	final protected function parseTitleDataDOM(
210
		$content, string $title_url,
211
		string $node_title_string, string $node_row_string,
212
		string $node_latest_string, string $node_chapter_string,
213
		string $failure_string = "") {
214
215
		if(!is_array($content)) {
216
			log_message('error', "{$this->site} : {$title_url} | Failed to grab URL (See above curl error)");
217
		} else {
218
			list('headers' => $headers, 'status_code' => $status_code, 'body' => $data) = $content;
0 ignored issues
show
Unused Code introduced by
The assignment to $headers is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
219
220
			if(!($status_code >= 200 && $status_code < 300)) {
221
				log_message('error', "{$this->site} : {$title_url} | Bad Status Code ({$status_code})");
222
			} else if(empty($data)) {
223
				log_message('error', "{$this->site} : {$title_url} | Data is empty? (Status code: {$status_code})");
224
			} else if($failure_string !== "" && strpos($data, $failure_string) !== FALSE) {
225
				log_message('error', "{$this->site} : {$title_url} | Failure string matched");
226
			} else {
227
				$data = $this->cleanTitleDataDOM($data); //This allows us to clean the DOM prior to parsing. It's faster to grab the only part we need THEN parse it.
228
229
				$dom = new DOMDocument();
230
				libxml_use_internal_errors(TRUE);
231
				$dom->loadHTML('<?xml encoding="utf-8" ?>' . $data);
232
				libxml_use_internal_errors(FALSE);
233
234
				$xpath = new DOMXPath($dom);
235
				$nodes_title = $xpath->query($node_title_string);
236
				$nodes_row   = $xpath->query($node_row_string);
237
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
238
					$firstRow      = $nodes_row->item(0);
239
					$nodes_latest  = $xpath->query($node_latest_string,  $firstRow);
240
241
					if($node_chapter_string !== '') {
242
						$nodes_chapter = $xpath->query($node_chapter_string, $firstRow);
243
					} else {
244
						$nodes_chapter = $nodes_row;
245
					}
246
247
					if($nodes_latest->length === 1 && $nodes_chapter->length === 1) {
248
						return [
249
							'nodes_title'   => $nodes_title->item(0),
250
							'nodes_latest'  => $nodes_latest->item(0),
251
							'nodes_chapter' => $nodes_chapter->item(0)
252
						];
253
					} else {
254
						log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (LATEST: {$nodes_latest->length} | CHAPTER: {$nodes_chapter->length})");
255
					}
256
				} else {
257
					log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (TITLE: {$nodes_title->length} | ROW: {$nodes_row->length})");
258
				}
259
			}
260
		}
261
262
		return FALSE;
263
	}
264
265
	/**
266
	 * Used by parseTitleDataDOM to clean the data prior to passing it to DOMDocument & DOMXPath.
267
	 * This is mostly done as an (assumed) speed improvement due to the reduced amount of DOM to parse, or simply just making it easier to parse with XPath.
268
	 *
269
	 * @param string $data
270
	 * @return string
271
	 */
272
	public function cleanTitleDataDOM(string $data) : string {
273
		return $data;
274
	}
275
276
	/**
277
	 * Used to follow a series on given site if supported.
278
	 *
279
	 * This is called by getTitleData if $firstGet is true (which occurs when the series is first being added to the DB).
280
	 *
281
	 * Most of the actual following is done by handleCustomFollow.
282
	 *
283
	 * @param string $data
284
	 * @param array  $extra
285
	 * @return array
286
	 */
287
	final public function doCustomFollow(string $data = "", array $extra = []) : array {
288
		$titleData = [];
289
		$this->handleCustomFollow(function($content, $id, closure $successCallback = NULL) use(&$titleData) {
290
			if(is_array($content)) {
291
				if(array_key_exists('status_code', $content)) {
292
					$statusCode = $content['status_code'];
293
					if($statusCode === 200) {
294
						$isCallable = is_callable($successCallback);
295
						if(($isCallable && $successCallback($content['body'])) || !$isCallable) {
296
							$titleData['followed'] = 'Y';
297
298
							log_message('info', "doCustomFollow succeeded for {$id}");
299
						} else {
300
							log_message('error', "doCustomFollow failed (Invalid response?) for {$id}");
301
						}
302
					} else {
303
						log_message('error', "doCustomFollow failed (Invalid status code ({$statusCode})) for {$id}");
304
					}
305
				} else {
306
					log_message('error', "doCustomFollow failed (Missing status code?) for {$id}");
307
				}
308
			} else {
309
				log_message('error', "doCustomFollow failed (Failed request) for {$id}");
310
			}
311
		}, $data, $extra);
312
		return $titleData;
313
	}
314
315
	/**
316
	 * Used by doCustomFollow to handle following series on sites.
317
	 *
318
	 * Uses get_content to get data.
319
	 *
320
	 * $callback must return ($content, $id, closure $successCallback = NULL).
321
	 * * $content is simply just the get_content data.
322
	 * * $id is the dbID. This should be passed by the $extra arr.
323
	 * * $successCallback is an optional success check to make sure the series was properly followed.
324
	 *
325
	 * @param callable $callback
326
	 * @param string   $data
327
	 * @param array    $extra
328
	 */
329
	public function handleCustomFollow(callable $callback, string $data = "", array $extra = []) {}
330
331
	/**
332
	 * Used to check the sites following page for new updates (if supported).
333
	 * This should work much like getTitleData, but instead checks the following page.
334
	 *
335
	 * This must return an array containing arrays of each of the chapters data.
336
	 */
337
	public function doCustomUpdate() {}
338
339
	/**
340
	 * Used by the custom updater to check if a chapter looks newer than the current one.
341
	 *
342
	 * This calls doCustomCheckCompare which handles the majority of the checking.
343
	 * NOTE: Depending on the site, you may need to call getChapterData to get the chapter number to be used with this.
344
	 *
345
	 * @param string $oldChapter
346
	 * @param string $newChapter
347
	 */
348
	public function doCustomCheck(string $oldChapter, string $newChapter) {}
349
350
	/**
351
	 * Used by doCustomCheck to check if a chapter looks newer than the current one.
352
	 * Chapter must be in a (v[0-9]+/)?c[0-9]+(\..+)? format.
353
	 *
354
	 * To avoid issues with the occasional off case, this will only ever return true if we are 100% sure that the new chapter is newer than the old one.
355
	 *
356
	 * @param array $oldChapterSegments
357
	 * @param array $newChapterSegments
358
	 * @return bool
359
	 */
360 12
	final public function doCustomCheckCompare(array $oldChapterSegments, array $newChapterSegments) : bool {
361
		//NOTE: We only need to check against the new chapter here, as that is what is used for confirming update.
362 12
		$status = FALSE;
363
364
		//Make sure we have a volume element
365 12
		if(count($oldChapterSegments) === 1) array_unshift($oldChapterSegments, 'v0');
366 12
		if(count($newChapterSegments) === 1) array_unshift($newChapterSegments, 'v0');
367
368 12
		$oldCount = count($oldChapterSegments);
369 12
		$newCount = count($newChapterSegments);
370 12
		if($newCount === $oldCount) {
371
			//Make sure chapter format looks correct.
372
			//NOTE: We only need to check newCount as we know oldCount is the same count.
373 12
			if($newCount === 2) {
374
				//FIXME: Can we loop this?
375 12
				$oldVolume = substr(array_shift($oldChapterSegments), 1);
376 12
				$newVolume = substr(array_shift($newChapterSegments), 1);
377
378
				//Forcing volume to 0 as TBD might not be the latest (although it can be, but that is covered by other checks)
379 12
				if(in_array($oldVolume, ['TBD', 'TBA', 'NA', 'LMT'])) $oldVolume = 0;
380 12
				if(in_array($newVolume, ['TBD', 'TBA', 'NA', 'LMT'])) $newVolume = 0;
381
382 12
				$oldVolume = floatval($oldVolume);
383 12
				$newVolume = floatval($newVolume);
384
			} else {
385
				$oldVolume = 0;
386
				$newVolume = 0;
387
			}
388 12
			$oldChapter = floatval(substr(array_shift($oldChapterSegments), 1));
389 12
			$newChapter = floatval(substr(array_shift($newChapterSegments), 1));
390
391 12
			if($newChapter > $oldChapter && ($oldChapter >= 10 && $newChapter >= 10)) {
392
				//$newChapter is higher than $oldChapter AND $oldChapter and $newChapter are both more than 10
393
				//This is intended to cover the /majority/ of valid updates, as we technically shouldn't have to check volumes.
394
395 4
				$status = TRUE;
396 8
			} elseif($newVolume > $oldVolume && ($oldChapter < 10 && $newChapter < 10)) {
397
				//This is pretty much just to match a one-off case where the site doesn't properly increment chapter numbers across volumes, and instead does something like: v1/c1..v1/c5, v2/c1..v1/c5 (and so on).
398 1
				$status = TRUE;
399 7
			} elseif($newVolume > $oldVolume && $newChapter >= $oldChapter) {
400
				//$newVolume is higher, and chapter is higher so no need to check chapter.
401 2
				$status = TRUE;
402 5
			} elseif($newChapter > $oldChapter) {
403
				//$newVolume isn't higher, but chapter is.
404
				$status = TRUE;
405
			}
406
		}
407
408 12
		return $status;
409
	}
410
}
411
412
abstract class Base_FoolSlide_Site_Model extends Base_Site_Model {
413
	public $titleFormat   = '/^[a-z0-9_-]+$/';
414
	public $chapterFormat = '/^en(?:-us)?\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
415
	public $customType    = 2;
416
417
	public $baseURL = '';
418
419
	public function getFullTitleURL(string $title_url) : string {
420
		return "{$this->baseURL}/series/{$title_url}";
421
	}
422
423
	public function getChapterData(string $title_url, string $chapter) : array {
424
		$chapter_parts = explode('/', $chapter); //returns #LANG#/#VOLUME#/#CHAPTER#/#CHAPTER_EXTRA#(/#PAGE#/)
425
		return [
426
			'url'    => "{$this->baseURL}/read/{$title_url}/{$chapter}/",
427
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
428
		];
429
	}
430
431
	public function getTitleData(string $title_url, bool $firstGet = FALSE) : ?array {
432
		$titleData = [];
433
434
		$jsonURL = "{$this->baseURL}/api/reader/comic/stub/{$title_url}/format/json";
435
		if($content = $this->get_content($jsonURL)) {
436
			$json = json_decode($content['body'], TRUE);
437
			if($json && isset($json['chapters']) && count($json['chapters']) > 0) {
438
				$titleData['title'] = trim($json['comic']['name']);
439
440
				//FoolSlide title API doesn't appear to let you sort (yet every other API method which has chapters does, so we need to sort ourselves..
441
				usort($json['chapters'], function($a, $b) {
442
					return floatval("{$b['chapter']['chapter']}.{$b['chapter']['subchapter']}") <=> floatval("{$a['chapter']['chapter']}.{$a['chapter']['subchapter']}");
443
				});
444
				$latestChapter = reset($json['chapters'])['chapter'];
445
446
				$latestChapterString = "{$latestChapter['language']}/{$latestChapter['volume']}/{$latestChapter['chapter']}";
447
				if($latestChapter['subchapter'] !== '0') {
448
					$latestChapterString .= "/{$latestChapter['subchapter']}";
449
				}
450
				$titleData['latest_chapter'] = $latestChapterString;
451
452
				//No need to use date() here since this is already formatted as such.
453
				$titleData['last_updated'] = ($latestChapter['updated'] !== '0000-00-00 00:00:00' ? $latestChapter['updated'] : $latestChapter['created']);
454
			}
455
		}
456
457
		return (!empty($titleData) ? $titleData : NULL);
458
	}
459
460
	//Since we're just checking the latest updates page and not a following page, we just need to simulate a follow.
461
	//TODO: It would probably be better to have some kind of var which says that the custom update uses a following page..
462
	public function handleCustomFollow(callable $callback, string $data = "", array $extra = []) {
463
		$content = ['status_code' => 200];
464
		$callback($content, $extra['id']);
465
	}
466
	public function doCustomUpdate() {
467
		$titleDataList = [];
468
469
		$jsonURL = "{$this->baseURL}/api/reader/chapters/orderby/desc_created/format/json";
470
		if(($content = $this->get_content($jsonURL)) && $content['status_code'] == 200) {
471
			$json = json_decode($content['body'], TRUE);
472
473
			//This should fix edge cases where chapters are uploaded in bulk in the wrong order (HelveticaScans does this with Mousou Telepathy).
474
			usort($json['chapters'], function($a, $b) {
475
				$a_date = new DateTime($a['chapter']['updated'] !== '0000-00-00 00:00:00' ? $a['chapter']['updated'] : $a['chapter']['created']);
476
				$b_date = new DateTime($b['chapter']['updated'] !== '0000-00-00 00:00:00' ? $b['chapter']['updated'] : $b['chapter']['created']);
477
				return $b_date <=> $a_date;
478
			});
479
480
			$parsedTitles = [];
481
			foreach($json['chapters'] as $chapterData) {
482
				if(!in_array($chapterData['comic']['stub'], $parsedTitles)) {
483
					$parsedTitles[] = $chapterData['comic']['stub'];
484
485
					$titleData = [];
486
					$titleData['title'] = trim($chapterData['comic']['name']);
487
488
					$latestChapter = $chapterData['chapter'];
489
490
					$latestChapterString = "en/{$latestChapter['volume']}/{$latestChapter['chapter']}";
491
					if($latestChapter['subchapter'] !== '0') {
492
						$latestChapterString .= "/{$latestChapter['subchapter']}";
493
					}
494
					$titleData['latest_chapter'] = $latestChapterString;
495
496
					//No need to use date() here since this is already formatted as such.
497
					$titleData['last_updated'] = ($latestChapter['updated'] !== '0000-00-00 00:00:00' ? $latestChapter['updated'] : $latestChapter['created']);
498
499
					$titleDataList[$chapterData['comic']['stub']] = $titleData;
500
				} else {
501
					//We already have title data for this title.
502
					continue;
503
				}
504
			}
505
		} else {
506
			log_message('error', "Custom updating failed for {$this->baseURL}.");
507
		}
508
509
		return $titleDataList;
510
	}
511
	public function doCustomCheck(string $oldChapterString, string $newChapterString) {
512
		$oldChapterSegments = explode('/', $this->getChapterData('', $oldChapterString)['number']);
513
		$newChapterSegments = explode('/', $this->getChapterData('', $newChapterString)['number']);
514
515
		$status = $this->doCustomCheckCompare($oldChapterSegments, $newChapterSegments);
516
517
		return $status;
518
	}
519
}
520