Completed
Push — master ( 6d5f3c...a28ed9 )
by Angus
03:43
created

EGScans::getFullTitleURL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
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
abstract class Site_Model extends CI_Model {
4
	public $site          = '';
5
	public $titleFormat   = '';
6
	public $chapterFormat = '';
7
8
	public function __construct() {
9
		parent::__construct();
10
11
		$this->load->database();
12
		
13
		$this->site = get_class($this);
14
	}
15
16
	abstract public function getFullTitleURL(string $title_url) : string;
17
18
	abstract public function getChapterData(string $title_url, string $chapter) : array;
19
20
	//TODO: When ci-phpunit-test supports PHP Parser 3.x, add " : ?array"
21
	abstract public function getTitleData(string $title_url, bool $firstGet = FALSE);
22
23
	public function isValidTitleURL(string $title_url) : bool {
24
		$success = (bool) preg_match($this->titleFormat, $title_url);
25
		if(!$success) log_message('error', "Invalid Title URL ({$this->site}): {$title_url}");
26
		return $success;
27
	}
28
	public function isValidChapter(string $chapter) : bool {
29
		$success = (bool) preg_match($this->chapterFormat, $chapter);
30
		if(!$success) log_message('error', "Invalid Chapter ({$this->site}): {$chapter}");
31
		return $success;
32
	}
33
34
	final protected function get_content(string $url, string $cookie_string = "", string $cookiejar_path = "", bool $follow_redirect = FALSE, bool $isPost = FALSE, array $postFields = []) {
35
		$ch = curl_init();
36
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
37
		curl_setopt($ch, CURLOPT_ENCODING , "gzip");
38
		//curl_setopt($ch, CURLOPT_VERBOSE, 1);
39
		curl_setopt($ch, CURLOPT_HEADER, 1);
40
41
		if($follow_redirect)        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
42
43
		if(!empty($cookie_string))  curl_setopt($ch, CURLOPT_COOKIE, $cookie_string);
44
		if(!empty($cookiejar_path)) curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiejar_path);
45
46
		//Some sites check the useragent for stuff, use a pre-defined user-agent to avoid stuff.
47
		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');
48
49
		//TODO: Check in a while if this being enabled still causes issues
50
		//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); //FIXME: This isn't safe, but it allows us to grab SSL URLs
51
52
		curl_setopt($ch, CURLOPT_URL, $url);
53
54
		if($isPost) {
55
			curl_setopt($ch,CURLOPT_POST, count($postFields));
56
			curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($postFields));
57
		}
58
59
		$response = curl_exec($ch);
60
		if($response === FALSE) {
61
			log_message('error', "curl failed with error: ".curl_errno($ch)." | ".curl_error($ch));
62
			//FIXME: We don't always account for FALSE return
63
			return FALSE;
64
		}
65
66
		$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
67
		$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
68
		$headers     = http_parse_headers(substr($response, 0, $header_size));
69
		$body        = substr($response, $header_size);
70
		curl_close($ch);
71
72
		return [
73
			'headers'     => $headers,
74
			'status_code' => $status_code,
75
			'body'        => $body
76
		];
77
	}
78
79
	/**
80
	 * @param array  $content
81
	 * @param string $title_url
82
	 * @param string $node_title_string
83
	 * @param string $node_row_string
84
	 * @param string $node_latest_string
85
	 * @param string $node_chapter_string
86
	 * @param string $failure_string
87
	 *
88
	 * @return DOMElement[]|false
89
	 */
90
	final protected function parseTitleDataDOM(
91
		$content, string $title_url,
92
		string $node_title_string, string $node_row_string,
93
		string $node_latest_string, string $node_chapter_string,
94
		string $failure_string = "") {
95
		//list('headers' => $headers, 'status_code' => $status_code, 'body' => $data) = $content; //TODO: PHP 7.1
96
97
		if(!is_array($content)) {
98
			log_message('error', "{$this->site} : {$title_url} | Failed to grab URL (See above curl error)");
99
		} else {
100
			$headers     = $content['headers'];
0 ignored issues
show
Unused Code introduced by
$headers 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...
101
			$status_code = $content['status_code'];
102
			$data        = $content['body'];
103
104
			if(!($status_code >= 200 && $status_code < 300)) {
105
				log_message('error', "{$this->site} : {$title_url} | Bad Status Code ({$status_code})");
106
			} else if(empty($data)) {
107
				log_message('error', "{$this->site} : {$title_url} | Data is empty? (Status code: {$status_code})");
108
			} else if($failure_string !== "" && strpos($data, $failure_string) !== FALSE) {
109
				log_message('error', "{$this->site} : {$title_url} | Failure string matched");
110
			} else {
111
				$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.
112
113
				$dom = new DOMDocument();
114
				libxml_use_internal_errors(TRUE);
115
				$dom->loadHTML($data);
116
				libxml_use_internal_errors(FALSE);
117
118
				$xpath = new DOMXPath($dom);
119
				$nodes_title = $xpath->query($node_title_string);
120
				$nodes_row   = $xpath->query($node_row_string);
121
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
122
					$firstRow      = $nodes_row->item(0);
123
					$nodes_latest  = $xpath->query($node_latest_string,  $firstRow);
124
125
					if($node_chapter_string !== '') {
126
						$nodes_chapter = $xpath->query($node_chapter_string, $firstRow);
127
					} else {
128
						$nodes_chapter = $nodes_row;
129
					}
130
131
					if($nodes_latest->length === 1 && $nodes_chapter->length === 1) {
132
						return [
133
							'nodes_title'   => $nodes_title->item(0),
134
							'nodes_latest'  => $nodes_latest->item(0),
135
							'nodes_chapter' => $nodes_chapter->item(0)
136
						];
137
					} else {
138
						log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (LATEST: {$nodes_latest->length} | CHAPTER: {$nodes_chapter->length})");
139
					}
140
				} else {
141
					log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (TITLE: {$nodes_title->length} | ROW: {$nodes_row->length})");
142
				}
143
			}
144
		}
145
146
		return FALSE;
147
	}
148
149
	public function cleanTitleDataDOM(string $data) : string {
150
		return $data;
151
	}
152
153
	//This has it's own function due to FoOlSlide being used a lot by fan translation sites, and the code being pretty much the same across all of them.
154
	final public function parseFoolSlide(string $fullURL, string $title_url) {
155
		$titleData = [];
156
157
		if($content = $this->get_content($fullURL, "", "", FALSE, TRUE, ['adult' => 'true'])) {
158
			$content['body'] = preg_replace('/^[\S\s]*(<article[\S\s]*)<\/article>[\S\s]*$/', '$1', $content['body']);
159
160
			$data = $this->parseTitleDataDOM(
161
				$content,
162
				$title_url,
163
				"//div[@class='large comic']/h1[@class='title']",
164
				"(//div[@class='list']/div[@class='group']/div[@class='title' and text() = 'Chapters']/following-sibling::div[@class='element'][1] | //div[@class='list']/div[@class='element'][1] | //div[@class='list']/div[@class='group'][1]/div[@class='element'][1])[1]",
165
				"div[@class='meta_r']",
166
				"div[@class='title']/a"
167
			);
168
			if($data) {
169
				$titleData['title'] = trim($data['nodes_title']->textContent);
170
171
				$link                        = (string) $data['nodes_chapter']->getAttribute('href');
172
				$titleData['latest_chapter'] = preg_replace('/.*\/read\/.*?\/(.*?)\/$/', '$1', $link);
173
174
				$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime((string) str_replace('.', '', explode(',', $data['nodes_latest']->nodeValue)[1])));
175
			}
176
		}
177
178
		return (!empty($titleData) ? $titleData : NULL);
179
	}
180
181
	public function doCustomFollow(string $data = "", array $extra = []) {}
182
	public function doCustomUpdate() {}
183
	public function doCustomCheck(string $oldChapter, string $newChapter) {}
184
}
185
class Sites_Model extends CI_Model {
186
	public function __get($name) {
187
		//TODO: Is this a good idea? There wasn't a good consensus on if this is good practice or not..
188
		//      It's probably a minor speed reduction, but that isn't much of an issue.
189
		//      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
190
		if(!class_exists($name) || !(get_parent_class($name) === 'Site_Model')) {
191
			parent::__get($name);
192
			return FALSE;
193
		} else {
194
			$this->loadSite($name);
195
			return $this->{$name};
196
		}
197
	}
198
199
	private function loadSite(string $siteName) {
200
		$this->{$siteName} = new $siteName();
201
	}
202
}
203
204
class MangaFox extends Site_Model {
205
	public $titleFormat   = '/^[a-z0-9_]+$/';
206
	public $chapterFormat = '/^(?:v[0-9a-zA-Z]+\/)?c[0-9\.]+$/';
207
208
	public function getFullTitleURL(string $title_url) : string {
209
		return "http://mangafox.me/manga/{$title_url}/";
210
	}
211
212
	public function getChapterData(string $title_url, string $chapter) : array {
213
		return [
214
			'url'    => "http://mangafox.me/manga/{$title_url}/{$chapter}/1.html",
215
			'number' => $chapter
216
		];
217
	}
218
219
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
220
		$titleData = [];
221
222
		$fullURL = $this->getFullTitleURL($title_url);
223
		$content = $this->get_content($fullURL);
224
225
		$data = $this->parseTitleDataDOM(
226
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 223 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
227
			$title_url,
228
			"//title",
229
			"//body/div[@id='page']/div[@class='left']/div[@id='chapters']/ul[1]/li[1]",
230
			"div/span[@class='date']",
231
			"div/h3/a"
232
		);
233
		if($data) {
234
			$titleData['title'] = html_entity_decode(explode(' Manga - Read ', $data['nodes_title']->textContent)[0]);
235
236
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
237
			$chapterURLSegments = explode('/', $link);
238
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
239
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
240
241
			if($firstGet) {
242
				$this->doCustomFollow($content['body']);
243
			}
244
		}
245
246
		return (!empty($titleData) ? $titleData : NULL);
247
	}
248
249
	//FIXME: This entire thing feels like an awful implementation....BUT IT WORKS FOR NOW.
250
	public function doCustomFollow(string $data = "", array $extra = []) {
251
		preg_match('/var sid=(?<id>[0-9]+);/', $data, $matches);
252
253
		$formData = [
254
			'action' => 'add',
255
			'sid'    => $matches['id']
256
		];
257
258
		$cookies = [
259
			"mfvb_userid={$this->config->item('mangafox_userid')}",
260
			"mfvb_password={$this->config->item('mangafox_password')}",
261
			"bmsort=last_chapter"
262
		];
263
		$content = $this->get_content('http://mangafox.me/ajax/bookmark.php', implode("; ", $cookies), "", TRUE, TRUE, $formData);
264
265
		return is_array($content) && in_array('status_code', $content) && $content['status_code'] === 200;
266
	}
267
	public function doCustomUpdate() {
268
		$titleDataList = [];
269
270
		$cookies = [
271
			"mfvb_userid={$this->config->item('mangafox_userid')}",
272
			"mfvb_password={$this->config->item('mangafox_password')}",
273
			"bmsort=last_chapter",
274
			"bmorder=za"
275
		];
276
		$content = $this->get_content('http://mangafox.me/bookmark/?status=currentreading&sort=last_chapter&order=za', implode("; ", $cookies), "", TRUE);
277
278
		if(!is_array($content)) {
279
			log_message('error', "{$this->site} /bookmark | Failed to grab URL (See above curl error)");
280
		} else {
281
			$headers     = $content['headers'];
0 ignored issues
show
Unused Code introduced by
$headers 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...
282
			$status_code = $content['status_code'];
283
			$data        = $content['body'];
284
285
			if(!($status_code >= 200 && $status_code < 300)) {
286
				log_message('error', "{$this->site} /bookmark | Bad Status Code ({$status_code})");
287
			} else if(empty($data)) {
288
				log_message('error', "{$this->site} /bookmark | Data is empty? (Status code: {$status_code})");
289
			} else {
290
				$data = preg_replace('/^[\s\S]+<ul id="bmlist">/', '<ul id="bmlist">', $data);
291
				$data = preg_replace('/<!-- end of bookmark -->[\s\S]+$/', '<!-- end of bookmark -->', $data);
292
293
				$dom = new DOMDocument();
294
				libxml_use_internal_errors(TRUE);
295
				$dom->loadHTML($data);
296
				libxml_use_internal_errors(FALSE);
297
298
				$xpath      = new DOMXPath($dom);
299
				$nodes_rows = $xpath->query("//ul[@id='bmlist']/li/div[@class='series_grp' and h2[@class='title']/span[@class='updatedch'] and dl]");
300
				if($nodes_rows->length > 0) {
301
					foreach($nodes_rows as $row) {
302
						$titleData = [];
303
304
						$nodes_title   = $xpath->query("h2[@class='title']/a[contains(@class, 'title')]", $row);
305
						$nodes_chapter = $xpath->query("dl/dt[1]/a[@class='chapter']", $row);
306
						$nodes_latest  = $xpath->query("dl/dt[1]/em/span[@class='timing']", $row);
307
308
						if($nodes_title->length === 1 && $nodes_chapter->length === 1 && $nodes_latest->length === 1) {
309
							$title = $nodes_title->item(0);
310
311
							$titleData['title'] = trim($title->textContent);
312
313
314
							$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $nodes_chapter->item(0)->getAttribute('href'));
315
							$chapterURLSegments = explode('/', $link);
316
							$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
317
318
							$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $nodes_latest->item(0)->nodeValue));
319
320
							$title_url = explode('/', $title->getAttribute('href'))[4];
321
							$titleDataList[$title_url] = $titleData;
322
						} else {
323
							log_message('error', "{$this->site}/Custom | Invalid amount of nodes (TITLE: {$nodes_title->length} | CHAPTER: {$nodes_chapter->length}) | LATEST: {$nodes_latest->length})");
324
						}
325
					}
326
				} else {
327
					log_message('error', '{$this->site} | Following list is empty?');
328
				}
329
			}
330
		}
331
		return $titleDataList;
332
	}
333
	public function doCustomCheck(string $oldChapterString, string $newChapterString) {
334
		$status = FALSE;
335
336
		$oldChapterSegments = explode('/', $oldChapterString);
337
		$newChapterSegments = explode('/', $newChapterString);
338
339
		//Although it's rare, it's possible for new chapters to have a different amount of segments to the oldChapter (or vice versa).
340
		//Since this can cause errors, we just throw a fail.
341
		$count = count($newChapterSegments);
342 View Code Duplication
		if($count === count($oldChapterSegments)) {
343
			if($count === 2) {
344
				//FIXME: This feels like a mess.
345
				$oldVolume = substr(array_shift($oldChapterSegments), 1);
346
				$newVolume = substr(array_shift($newChapterSegments), 1);
347
348
				if(in_array($oldVolume, ['TBD', 'TBA', 'NA'])) $oldVolume = 999;
349
				if(in_array($newVolume, ['TBD', 'TBA', 'NA'])) $newVolume = 999;
350
351
				$oldVolume = floatval($oldVolume);
352
				$newVolume = floatval($newVolume);
353
			} else {
354
				$oldVolume = 0;
355
				$newVolume = 0;
356
			}
357
			$oldChapter = floatval(substr(array_shift($oldChapterSegments), 1));
358
			$newChapter = floatval(substr(array_shift($newChapterSegments), 1));
359
360
			if($newVolume > $oldVolume) {
361
				//$newVolume is higher, no need to check chapter.
362
				$status = TRUE;
363
			} elseif($newChapter > $oldChapter) {
364
				//$newVolume isn't higher, but chapter is.
365
				$status = TRUE;
366
			}
367
		}
368
369
		return $status;
370
	}
371
}
372
373
class MangaHere extends Site_Model {
374
	public $titleFormat   = '/^[a-z0-9_]+$/';
375
	public $chapterFormat = '/^(?:v[0-9]+\/)?c[0-9]+(?:\.[0-9]+)?$/';
376
377
	public function getFullTitleURL(string $title_url) : string {
378
		return "http://www.mangahere.co/manga/{$title_url}/";
379
	}
380
381
	public function getChapterData(string $title, string $chapter) : array {
382
		return [
383
			'url'    => "http://www.mangahere.co/manga/{$title}/{$chapter}/",
384
			'number' => $chapter
385
		];
386
	}
387
388
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
389
		$titleData = [];
390
391
		$fullURL = $this->getFullTitleURL($title_url);
392
		$content = $this->get_content($fullURL);
393
394
		$data = $this->parseTitleDataDOM(
395
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 392 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
396
			$title_url,
397
			"//meta[@property='og:title']/@content",
398
			"//body/section/article/div/div[@class='manga_detail']/div[@class='detail_list']/ul[1]/li[1]",
399
			"span[@class='right']",
400
			"span[@class='left']/a",
401
			"<div class=\"error_text\">Sorry, the page you have requested can’t be found."
402
		);
403
		if($data) {
404
			$titleData['title'] = $data['nodes_title']->textContent;
405
406
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
407
			$chapterURLSegments = explode('/', $link);
408
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
409
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
410
		}
411
412
		return (!empty($titleData) ? $titleData : NULL);
413
	}
414
}
415
416
class Batoto extends Site_Model {
417
	//Batoto is a bit tricky to track. Unlike MangaFox and MangaHere, it doesn't store anything in the title_url, which means we have to get the data via other methods.
418
	//One problem we have though, is the tracker must support multiple sites, so this means we need to do some weird things to track Batoto.
419
	//title_url is stored like: "ID:--:LANGUAGE"
420
	//chapter_urls are stored like "CHAPTER_ID:--:CHAPTER_NUMBER"
421
422
	public $titleFormat   = '/^[0-9]+:--:(?:English|Spanish|French|German|Portuguese|Turkish|Indonesian|Greek|Filipino|Italian|Polish|Thai|Malay|Hungarian|Romanian|Arabic|Hebrew|Russian|Vietnamese|Dutch)$/';
423
	//FIXME: We're not validating the chapter name since we don't know what all the possible valid characters can be
424
	//       Preferably we'd just use /^[0-9a-z]+:--:(v[0-9]+\/)?c[0-9]+(\.[0-9]+)?$/
425
	public $chapterFormat = '/^[0-9a-z]+:--:.+$/';
426
427
	public function getFullTitleURL(string $title_string) : string {
428
		//FIXME: This does not point to the language specific title page. Should ask if it is possible to set LANG as arg?
429
		//FIXME: This points to a generic URL which will redirect according to the ID. Preferably we'd try and get the exact URL from the title, but we can't pass it here.
430
		$title_parts = explode(':--:', $title_string);
431
		return "http://bato.to/comic/_/comics/-r".$title_parts[0];
432
	}
433
434 View Code Duplication
	public function getChapterData(string $title_string, string $chapter) : array {
435
		//$title_string isn't used here.
436
437
		$chapter_parts = explode(':--:', $chapter);
438
		return [
439
			'url'    => "http://bato.to/reader#" . $chapter_parts[0],
440
			'number' => $chapter_parts[1]
441
		];
442
	}
443
444
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
445
		$titleData = [];
446
447
		$title_parts = explode(':--:', $title_url);
448
		$fullURL     = $this->getFullTitleURL($title_url);
449
		$lang        = $title_parts[1]; //TODO: Validate title_lang from array?
450
451
452
		//Bato.to is annoying and locks stuff behind auth. See: https://github.com/DakuTree/manga-tracker/issues/14#issuecomment-233830855
453
		$cookies = [
454
			"lang_option={$lang}",
455
			"member_id={$this->config->item('batoto_cookie_member_id')}",
456
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
457
		];
458
		$content = $this->get_content($fullURL, implode("; ", $cookies), "", TRUE);
459
460
		$data = $this->parseTitleDataDOM(
461
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($full...', $cookies), '', TRUE) on line 458 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
462
			$title_url,
463
			"//h1[@class='ipsType_pagetitle']",
464
			"//table[contains(@class, 'chapters_list')]/tbody/tr[2]",
465
			"td[last()]",
466
			"td/a[contains(@href,'reader')]",
467
			">Register now<"
468
		);
469
		if($data) {
470
			$titleData['title'] = html_entity_decode(trim($data['nodes_title']->textContent));
471
472
			///^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/
473
			preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($data['nodes_chapter']->nodeValue), $text);
474
			$titleData['latest_chapter'] = substr($data['nodes_chapter']->getAttribute('href'), 22) . ':--:' . ((!empty($text['volume']) ? 'v'.$text['volume'].'/' : '') . 'c'.$text['chapter'] . (!empty($text['extra']) ? '-'.$text['extra'] : ''));
475
476
			$dateString = $data['nodes_latest']->nodeValue;
477
			if($dateString == 'An hour ago') {
478
				$dateString = '1 hour ago';
479
			}
480
			$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
481
482
			if($firstGet && $lang == 'English') {
483
				//FIXME: English is forced due for now. See #78.
484
				$this->doCustomFollow($content['body'], ['id' => $title_parts[0], 'lang' => $lang]);
485
			}
486
		}
487
488
		return (!empty($titleData) ? $titleData : NULL);
489
	}
490
491
	public function cleanTitleDataDOM(string $data) : string {
492
		$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
493
		$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
494
		$data = preg_replace('/<div id=\'commentsStart\' class=\'ipsBox\'>[\s\S]+$/', '</div></div><!-- end mainContent -->', $data);
495
496
		return $data;
497
	}
498
499
	//FIXME: This entire thing feels like an awful implementation....BUT IT WORKS FOR NOW.
500
	public function doCustomFollow(string $data = "", array $extra = []) {
501
		preg_match('/ipb\.vars\[\'secure_hash\'\]\s+=\s+\'(?<secure_hash>[0-9a-z]+)\';[\s\S]+ipb\.vars\[\'session_id\'\]\s+=\s+\'(?<session_id>[0-9a-z]+)\';/', $data, $text);
502
503
		$params = [
504
			's'          => $text['session_id'],
505
			'app'        => 'core',
506
			'module'     => 'ajax',
507
			'section'    => 'like',
508
			'do'         => 'save',
509
			'secure_key' => $text['secure_hash'],
510
			'f_app'      => 'ccs',
511
			'f_area'     => 'ccs_custom_database_3_records',
512
			'f_relid'    => $extra['id']
513
		];
514
		$formData = [
515
			'like_notify' => '0',
516
			'like_freq'   => 'immediate',
517
			'like_anon'   => '0'
518
		];
519
520
		$cookies = [
521
			"lang_option={$extra['lang']}",
522
			"member_id={$this->config->item('batoto_cookie_member_id')}",
523
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
524
		];
525
		$content = $this->get_content('http://bato.to/forums/index.php?'.http_build_query($params), implode("; ", $cookies), "", TRUE, TRUE, $formData);
526
527
		return is_array($content) && in_array('status_code', $content) && $content['status_code'] === 200;
528
	}
529
	public function doCustomUpdate() {
530
		$titleDataList = [];
531
532
		$cookies = [
533
			"lang_option=English", //FIXME: English is forced due for now. See #78.
534
			"member_id={$this->config->item('batoto_cookie_member_id')}",
535
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
536
		];
537
		$content = $this->get_content("http://bato.to/myfollows", implode("; ", $cookies), "", TRUE);
538
		if(!is_array($content)) {
539
			log_message('error', "{$this->site} /myfollows | Failed to grab URL (See above curl error)");
540
		} else {
541
			$headers     = $content['headers'];
0 ignored issues
show
Unused Code introduced by
$headers 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...
542
			$status_code = $content['status_code'];
543
			$data        = $content['body'];
544
545
			if(!($status_code >= 200 && $status_code < 300)) {
546
				log_message('error', "{$this->site} /myfollows | Bad Status Code ({$status_code})");
547
			} else if(empty($data)) {
548
				log_message('error', "{$this->site} /myfollows | Data is empty? (Status code: {$status_code})");
549
			} else {
550
				$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
551
				$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
552
553
				$dom = new DOMDocument();
554
				libxml_use_internal_errors(TRUE);
555
				$dom->loadHTML($data);
556
				libxml_use_internal_errors(FALSE);
557
558
				$xpath      = new DOMXPath($dom);
559
				$nodes_rows = $xpath->query("//table[contains(@class, 'chapters_list')]/tbody/tr[position()>1]");
560
				if($nodes_rows->length > 0) {
561
					foreach($nodes_rows as $row) {
562
						$titleData = [];
563
564
						$nodes_title   = $xpath->query("td[2]/a[1]", $row);
565
						$nodes_chapter = $xpath->query("td[2]/a[2]", $row);
566
						$nodes_lang    = $xpath->query("td[3]/div", $row);
567
						$nodes_latest  = $xpath->query("td[5]", $row);
568
569
						if($nodes_lang->length === 1 && $nodes_lang->item(0)->getAttribute('title') == 'English') {
570
							if($nodes_title->length === 1 && $nodes_chapter->length === 1 && $nodes_latest->length === 1) {
571
								$title = $nodes_title->item(0);
572
573
								preg_match('/(?<id>[0-9]+)$/', $title->getAttribute('href'), $title_url_arr);
574
								$title_url = "{$title_url_arr['id']}:--:English"; //FIXME: English is currently forced, see #78
575
576
								if(!array_key_exists($title_url, $titleDataList)) {
577
									$titleData['title'] = trim($title->textContent);
578
579
									$chapter = $nodes_chapter->item(0);
580
									preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($chapter->nodeValue), $text);
581
									$titleData['latest_chapter'] = substr($chapter->getAttribute('href'), 8) . ':--:' . ((!empty($text['volume']) ? 'v' . $text['volume'] . '/' : '') . 'c' . $text['chapter'] . (!empty($text['extra']) ? '-' . $text['extra'] : ''));
582
583
									$dateString = $nodes_latest->item(0)->nodeValue;
584
									if($dateString == 'An hour ago') {
585
										$dateString = '1 hour ago';
586
									}
587
									$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
588
589
590
									$titleDataList[$title_url] = $titleData;
591
								}
592
							} else {
593
								log_message('error', "{$this->site}/Custom | Invalid amount of nodes (TITLE: {$nodes_title->length} | CHAPTER: {$nodes_chapter->length}) | LATEST: {$nodes_latest->length})");
594
							}
595
						}
596
					}
597
				} else {
598
					log_message('error', '{$this->site} | Following list is empty?');
599
				}
600
			}
601
		}
602
		return $titleDataList;
603
	}
604
	public function doCustomCheck(string $oldChapterString, string $newChapterString) {
605
		$status = FALSE;
606
607
		$oldChapterSegments = explode('/', $this->getChapterData('', $oldChapterString)['number']);
608
		$newChapterSegments = explode('/', $this->getChapterData('', $newChapterString)['number']);
609
610
		//Although it's rare, it's possible for new chapters to have a different amount of segments to the oldChapter (or vice versa).
611
		//Since this can cause errors, we just throw a fail.
612
		$count = count($newChapterSegments);
613 View Code Duplication
		if($count === count($oldChapterSegments)) {
614
			if($count === 2) {
615
				//FIXME: This feels like a mess.
616
				$oldVolume = substr(array_shift($oldChapterSegments), 1);
617
				$newVolume = substr(array_shift($newChapterSegments), 1);
618
619
				if(in_array($oldVolume, ['TBD', 'TBA', 'NA'])) $oldVolume = 999;
620
				if(in_array($newVolume, ['TBD', 'TBA', 'NA'])) $newVolume = 999;
621
622
				$oldVolume = floatval($oldVolume);
623
				$newVolume = floatval($newVolume);
624
			} else {
625
				$oldVolume = 0;
626
				$newVolume = 0;
627
			}
628
			$oldChapter = floatval(substr(array_shift($oldChapterSegments), 1));
629
			$newChapter = floatval(substr(array_shift($newChapterSegments), 1));
630
631
			if($newVolume > $oldVolume) {
632
				//$newVolume is higher, no need to check chapter.
633
				$status = TRUE;
634
			} elseif($newChapter > $oldChapter) {
635
				//$newVolume isn't higher, but chapter is.
636
				$status = TRUE;
637
			}
638
		}
639
640
		return $status;
641
	}
642
}
643
644
class DynastyScans extends Site_Model {
645
	//FIXME: This has some major issues. SEE: https://github.com/DakuTree/manga-tracker/issues/58
646
647
	public $titleFormat   = '/^[a-z0-9_]+:--:(?:0|1)$/';
648
	public $chapterFormat = '/^[0-9a-z_]+$/';
649
650
	public function getFullTitleURL(string $title_string) : string {
651
		$title_parts = explode(':--:', $title_string);
652
		$url_type = ($title_parts[1] == '0' ? 'series' : 'chapters');
653
654
		return 'https://dynasty-scans.com/'.$url_type.'/'.$title_parts[0];
655
	}
656
657
	public function getChapterData(string $title_string, string $chapter) : array {
658
		$title_parts = explode(':--:', $title_string);
659
		/* Known chapter url formats (# is numbers):
660
		       chapters_#A_#B - Ch#A-#B
661
		       ch_#A          - Ch#A
662
		       ch_#A_#B       - Ch#A.#B
663
		       <NOTHING>      - Oneshot (This is passed as "oneshot")
664
		*/
665
666
		$chapterData = [
667
			'url'    => 'https://dynasty-scans.com/chapters/' . $title_parts[0].'_'.$chapter,
668
			'number' => ''
669
		];
670
671
		if($chapter == 'oneshot') {
672
			$chapterData['number'] = 'oneshot';
673
		} else {
674
			$chapter = preg_replace("/^([a-zA-Z]+)/", '$1_', $chapter);
675
			$chapterSegments = explode('_', $chapter);
676
			switch($chapterSegments[0]) {
677
				case 'ch':
678
					$chapterData['number'] = 'c'.$chapterSegments[1].(isset($chapterSegments[2]) && !empty($chapterSegments[2]) ? '.'.$chapterSegments[2] : '');
679
					break;
680
681
				case 'chapters':
682
					//This is barely ever used, but I have seen it.
683
					$chapterData['number'] = 'c'.$chapterSegments[1].'-'.$chapterSegments[2];
684
					break;
685
686
				default:
687
					//TODO: FALLBACK, ALERT ADMIN?
688
					$chapterData['number'] = $chapter;
689
					break;
690
			}
691
		}
692
		return $chapterData;
693
	}
694
695
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
696
		$titleData = [];
697
698
		$fullURL = $this->getFullTitleURL($title_url);
699
		$content = $this->get_content($fullURL);
700
701
		$title_parts = explode(':--:', $title_url);
702
		switch($title_parts[1]) {
703
			case '0':
704
				//Normal series.
705
				$data = $this->parseTitleDataDOM(
706
					$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 699 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
707
					$title_url,
708
					"//h2[@class='tag-title']/b[1]",
709
					"(//dl[@class='chapter-list']/dd[a[contains(@href,'/chapters/')]])[last()]",
710
					"small",
711
					"a[@class='name']"
712
				);
713
				if($data) {
714
					$titleData['title'] = $data['nodes_title']->textContent;
715
					//In cases where the series is a doujin, try and prepend the copyright.
716
					preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $content['body'], $matchesD);
717
					if(!empty($matchedD) && substr($matchesD[1], 0, -7) !== 'Original') {
0 ignored issues
show
Bug introduced by
The variable $matchedD seems to never exist, and therefore empty should always return true. 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...
718
						$titleData['title'] = substr($matchesD[1], 0, -7).' - '.$titleData['title'];
719
					}
720
721
					$chapterURLSegments = explode('/', (string) $data['nodes_chapter']->getAttribute('href'));
722
					if (strpos($chapterURLSegments[2], $title_parts[0]) !== false) {
723
						$titleData['latest_chapter'] = substr($chapterURLSegments[2], strlen($title_parts[0]) + 1);
724
					} else {
725
						$titleData['latest_chapter'] = $chapterURLSegments[2];
726
					}
727
728
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime(str_replace("'", '', substr((string) $data['nodes_latest']->textContent, 9))));
729
				}
730
				break;
731
732
			case '1':
733
				//Oneshot.
734
				$data = $content['body'];
735
736
				preg_match('/<b>.*<\/b>/', $data, $matchesT);
737
				preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $data, $matchesD);
738
				$titleData['title'] = (!empty($matchesD) ? ($matchesD[1] !== 'Original' ? $matchesD[1].' - ' : '') : '') . substr($matchesT[0], 3, -4);
739
740
				$titleData['latest_chapter'] = 'oneshot'; //This will never change
741
742
				preg_match('/<i class="icon-calendar"><\/i> (.*)<\/span>/', $data, $matches);
743
				$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime($matches[1]));
744
745
				//Oneshots are special, and really shouldn't need to be re-tracked
746
				$titleData['status'] = '2';
747
				break;
748
749
			default:
750
				//something went wrong
751
				break;
752
		}
753
		return (!empty($titleData) ? $titleData : NULL);
754
	}
755
}
756
757
class MangaPanda extends Site_Model {
758
	//NOTE: MangaPanda has manga pages under the root URL, so we need to filter out pages we know that aren't manga.
759
	public $titleFormat   = '/^(?!(?:latest|search|popular|random|alphabetical|privacy)$)([a-z0-9-]+)$/';
760
	public $chapterFormat = '/^[0-9]+$/';
761
762
	public function getFullTitleURL(string $title_url) : string {
763
		return "http://www.mangapanda.com/{$title_url}";
764
	}
765
766
	public function getChapterData(string $title_url, string $chapter) : array {
767
		return [
768
			'url'    => "http://www.mangapanda.com/{$title_url}/{$chapter}/",
769
			'number' => 'c'.$chapter
770
		];
771
	}
772
773 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
774
		$titleData = [];
775
776
		$fullURL = $this->getFullTitleURL($title_url);
777
		$content = $this->get_content($fullURL);
778
779
		$data = $this->parseTitleDataDOM(
780
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 777 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
781
			$title_url,
782
			"//h2[@class='aname']",
783
			"(//table[@id='listing']/tr)[last()]",
784
			"td[2]",
785
			"td[1]/a"
786
		);
787
		if($data) {
788
			$titleData['title'] = $data['nodes_title']->textContent;
789
790
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
791
792
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
793
		}
794
795
		return (!empty($titleData) ? $titleData : NULL);
796
	}
797
}
798
799
class MangaStream extends Site_Model {
800
	public $titleFormat   = '/^[a-z0-9_]+$/';
801
	public $chapterFormat = '/^(.*?)\/[0-9]+$/';
802
803
	public function getFullTitleURL(string $title_url) : string {
804
		return "https://readms.net/manga/{$title_url}/";
805
	}
806
807
	public function getChapterData(string $title_url, string $chapter) : array {
808
		return [
809
			'url'    => "https://readms.net/r/{$title_url}/{$chapter}",
810
			'number' => 'c'.explode('/', $chapter)[0]
811
		];
812
	}
813
814 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
815
		$titleData = [];
816
817
		$fullURL = $this->getFullTitleURL($title_url);
818
		$content = $this->get_content($fullURL);
819
820
		$data = $this->parseTitleDataDOM(
821
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 818 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
822
			$title_url,
823
			"//div[contains(@class, 'content')]/div[1]/h1",
824
			"//div[contains(@class, 'content')]/div[1]/table/tr[2]",
825
			"td[2]",
826
			"td[1]/a",
827
			"<h1>Page Not Found</h1>"
828
		);
829
		if($data) {
830
			$titleData['title'] = $data['nodes_title']->textContent;
831
832
			$titleData['latest_chapter'] = preg_replace('/^.*\/(.*?\/[0-9]+)\/[0-9]+$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
833
834
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
835
		}
836
837
		return (!empty($titleData) ? $titleData : NULL);
838
	}
839
}
840
841
class WebToons extends Site_Model {
842
	/* Webtoons.com has a very weird and pointless URL format.
843
	   TITLE URL:   /#LANG#/#GENRE#/#TITLE#/list?title_no=#TITLEID#
844
	   RSS URL:     /#LANG#/#GENRE#/#TITLE#/rss?title_no=#TITLEID#
845
	   CHAPTER URL: /#LANG#/#GENRE#/#TITLE#/#CHAPTER#/viewer?title_no=#TITLEID#&episode_no=#CHAPTERID#
846
847
	   For both the title and chapter URLs, only the TITLEID and CHAPTERID are needed. Everything else can be anything at all (Well, alphanumeric at least).
848
	   The RSS URL however, requires everything to be exactly correct. I have no idea why this is, but it does mean we need to store all that info too.
849
	   We <could> not use the RSS url, and just parse via the title url, but rss is much better in the long run as it shouldn't change much.
850
851
	   FORMATS:
852
	   TITLE_URL: ID:--:LANG:--:TITLE:--:GENRE
853
	   CHAPTER:   ID:--:CHAPTER_N
854
	*/
855
	//private $validLang = ['en', 'zh-hant', 'zh-hans', 'th', 'id'];
856
857
	public $titleFormat   = '/^[0-9]+:--:(?:en|zh-hant|zh-hans|th|id):--:[a-z0-9-]+:--:(?:drama|fantasy|comedy|action|slice-of-life|romance|superhero|thriller|sports|sci-fi)$/';
858
	public $chapterFormat = '/^[0-9]+:--:.*$/';
859
860
	public function getFullTitleURL(string $title_url) : string {
861
		$title_parts = explode(':--:', $title_url);
862
		return "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/list?title_no={$title_parts[0]}/";
863
	}
864
865
	public function getChapterData(string $title_url, string $chapter) : array {
866
		$title_parts   = explode(':--:', $title_url);
867
		$chapter_parts = explode(':--:', $chapter);
868
869
		return [
870
			'url'    => "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/{$chapter_parts[1]}/viewer?title_no={$title_parts[0]}&episode_no={$chapter_parts[0]}",
871
			'number' => $chapter_parts[1] //TODO: Possibly replace certain formats in here? Since webtoons doesn't have a standard chapter format
872
		];
873
	}
874
875
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
876
		$titleData = [];
877
878
		//FIXME: We don't use parseTitleDOM here due to using rss. Should probably have an alternate method for XML parsing.
879
880
		//NOTE: getTitleData uses a different FullTitleURL due to it grabbing the rss ver. instead.
881
		$title_parts = explode(':--:', $title_url);
882
		$fullURL = "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/rss?title_no={$title_parts[0]}";
883
884
		$content = $this->get_content($fullURL);
885
		$data = $content['body'];
886
		if($data !== 'Can\'t find the manga series.') { //FIXME: We should check for he proper error here.
887
			$xml = simplexml_load_string($data) or die("Error: Cannot create object");
888
			if(isset($xml->{'channel'}->item[0])) {
889
				$titleData['title'] = trim((string) $xml->{'channel'}->title);
890
891
				$chapterURLSegments = explode('/', ((string) $xml->{'channel'}->item[0]->link));
892
				$titleData['latest_chapter'] = preg_replace('/^.*?([0-9]+)$/', '$1', $chapterURLSegments[7]) . ':--:' . $chapterURLSegments[6];
893
				$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $xml->{'channel'}->item[0]->pubDate));
894
			}
895
		} else {
896
			log_message('error', "Series missing? (WebToons): {$title_url}");
897
			return NULL;
898
		}
899
900
		return (!empty($titleData) ? $titleData : NULL);
901
	}
902
}
903
904
class KissManga extends Site_Model {
905
	/* This site is a massive pain in the ass. The only reason I'm supporting it is it's one of the few aggregator sites which actually support more risqué manga.
906
	   The main problem with this site is it has some form of bot protection. To view any part of the site normally, you need a cookie set by the bot protection.
907
908
	   To generate this cookie, we need three variables. Two are static, but the other is generated by randomly generated JS on the page.
909
	   The randomly generated JS is the troublesome part. We can't easily parse this with PHP. Both V8JS & SpiderMonkey refuse to build properly for me, so that rules that out.
910
	   The other option is using regex, but that is a rabbit hole I don't want to touch with a ten-foot pole.
911
912
	   To make the entire site work, I've built a python script to handle grabbing this cookie. This is grabbed & updated at the same time the manga are updated. The script saves the cookiejar which the PHP later reads.
913
	   The cookie has a length of 1 year, but I don't think it actually lasts that long, so we update every 6hours instead.
914
	   I should probably also mention that the cookie generated also uses your user-agent, so if it changes the cookie will break.
915
	*/
916
917
	public $titleFormat   = '/^[A-Za-z0-9-]+$/';
918
	public $chapterFormat = '/^.*?:--:[0-9]+$/';
919
920
	public function getFullTitleURL(string $title_url) : string {
921
		return "http://kissmanga.com/Manga/{$title_url}";
922
	}
923
924 View Code Duplication
	public function getChapterData(string $title_url, string $chapter) : array {
925
		$chapter_parts = explode(':--:', $chapter);
926
927
		return [
928
			'url'    => "http://kissmanga.com/Manga/{$title_url}/{$chapter_parts[0]}?id={$chapter_parts[1]}",
929
			//FIXME: KM has an extremely inconsistant chapter format which makes it difficult to parse.
930
			'number' => /*preg_replace('/--.*?$/', '', */$chapter_parts[0]/*)*/
931
		];
932
	}
933
934
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
935
		$titleData = [];
936
937
		//Check if cookiejar is a day old (so we can know if something went wrong)
938
		$cookiejar_path = str_replace("public/", "_scripts/cookiejar", FCPATH);
939
		$cookie_last_updated = filemtime($cookiejar_path);
940
		if($cookie_last_updated && ((time() - 86400) < $cookie_last_updated)) {
941
942
			$fullURL = $this->getFullTitleURL($title_url);
943
944
			$content = $this->get_content($fullURL, '', $cookiejar_path);
945
			$data = $content['body'];
946
			if(strpos($data, 'containerRoot') !== FALSE) {
947
				//FIXME: For whatever reason, we can't grab the entire div without simplexml shouting at us
948
				$data = preg_replace('/^[\S\s]*(<div id="leftside">[\S\s]*)<div id="rightside">[\S\s]*$/', '$1', $data);
949
950
				$dom = new DOMDocument();
951
				libxml_use_internal_errors(true);
952
				$dom->loadHTML($data);
953
				libxml_use_internal_errors(false);
954
955
				$xpath = new DOMXPath($dom);
956
957
				$nodes_title = $xpath->query("//a[@class='bigChar']");
958
				$nodes_row   = $xpath->query("//table[@class='listing']/tr[3]");
959
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
960
					$titleData['title'] = $nodes_title->item(0)->textContent;
961
962
					$firstRow      = $nodes_row->item(0);
963
					$nodes_latest  = $xpath->query("td[2]",   $firstRow);
964
					$nodes_chapter = $xpath->query("td[1]/a", $firstRow);
965
966
					$link = (string) $nodes_chapter->item(0)->getAttribute('href');
967
					$chapterURLSegments = explode('/', preg_replace('/\?.*$/', '', $link));
968
					$titleData['latest_chapter'] = $chapterURLSegments[3] . ':--:' . preg_replace('/.*?([0-9]+)$/', '$1', $link);
969
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $nodes_latest->item(0)->textContent));
970
				}
971
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
972
				//TODO: Throw ERRORS;
973
			}
974
		} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
975
			//Do nothing, wait until next update.
976
			//TODO: NAG ADMIN??
977
		}
978
979
		return (!empty($titleData) ? $titleData : NULL);
980
	}
981
}
982
983
class GameOfScanlation extends Site_Model {
984
	public $titleFormat   = '/^[a-z0-9\.-]+$/';
985
	public $chapterFormat = '/^[a-z0-9\.-]+$/';
986
987
	public function getFullTitleURL(string $title_url) : string {
988
		/* NOTE: GoS is a bit weird in that it has two separate title URL formats. One uses /projects/ and the other uses /fourms/.
989
		         The bad thing is these are interchangeable, despite them showing the exact same listing page.
990
		         Thankfully the title_url of manga which use /forums/ seem to be appended with ".%ID%" which means we can easily check them. */
991
992
		if (strpos($title_url, '.') !== FALSE) {
993
			$format = "https://gameofscanlation.moe/forums/{$title_url}/";
994
		} else {
995
			$format = "https://gameofscanlation.moe/projects/{$title_url}/";
996
		}
997
		return $format;
998
	}
999
1000
	public function getChapterData(string $title_url, string $chapter) : array {
1001
		return [
1002
			'url'    => "https://gameofscanlation.moe/projects/".preg_replace("/\\.[0-9]+$/", "", $title_url).'/'.$chapter.'/',
1003
			'number' => preg_replace("/chapter-/", "c", preg_replace("/\\.[0-9]+$/", "", $chapter))
1004
		];
1005
	}
1006
1007 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1008
		$titleData = [];
1009
1010
		$fullURL = $this->getFullTitleURL($title_url);
1011
1012
		$content = $this->get_content($fullURL);
1013
1014
		$data = $this->parseTitleDataDOM(
1015
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 1012 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1016
			$title_url,
1017
			"//meta[@property='og:title']",
1018
			"//ol[@class='discussionListItems']/li[1]/div[@class='home_list']/ul/li/div[@class='list_press_text']",
1019
			"p[@class='author']/span|p[@class='author']/abbr",
1020
			"p[@class='text_work']/a"
1021
		);
1022
		if($data) {
1023
			$titleData['title'] = trim(html_entity_decode($data['nodes_title']->getAttribute('content')));
1024
1025
			$titleData['latest_chapter'] = preg_replace('/^projects\/.*?\/(.*?)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
1026
1027
			$titleData['last_updated'] =  date("Y-m-d H:i:s",(int) $data['nodes_latest']->getAttribute('title'));
1028
		}
1029
1030
		return (!empty($titleData) ? $titleData : NULL);
1031
	}
1032
}
1033
1034
class MangaCow extends Site_Model {
1035
	public $titleFormat   = '/^[a-zA-Z0-9_]+$/';
1036
	public $chapterFormat = '/^[0-9]+$/';
1037
1038
	public function getFullTitleURL(string $title_url) : string {
1039
		return "http://mngcow.co/{$title_url}/";
1040
	}
1041
1042
	public function getChapterData(string $title_url, string $chapter) : array {
1043
		return [
1044
			'url'    => $this->getFullTitleURL($title_url).$chapter.'/',
1045
			'number' => "c{$chapter}"
1046
		];
1047
	}
1048
1049 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1050
		$titleData = [];
1051
1052
		$fullURL = $this->getFullTitleURL($title_url);
1053
1054
		$content = $this->get_content($fullURL);
1055
1056
		$data = $this->parseTitleDataDOM(
1057
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 1054 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1058
			$title_url,
1059
			"//h4",
1060
			"//ul[contains(@class, 'mng_chp')]/li[1]/a[1]",
1061
			"b[@class='dte']",
1062
			"",
1063
			"404 Page Not Found"
1064
		);
1065
		if($data) {
1066
			$titleData['title'] = trim($data['nodes_title']->textContent);
1067
1068
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
1069
1070
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) substr($data['nodes_latest']->getAttribute('title'), 13)));
1071
		}
1072
1073
		return (!empty($titleData) ? $titleData : NULL);
1074
	}
1075
}
1076
1077
class EGScans extends Site_Model {
1078
	public $titleFormat   = '/^[A-Za-z0-9\-_\!,]+$/';
1079
	public $chapterFormat = '/^Chapter_[0-9]+(?:_extra)?$/';
1080
1081
	public function getFullTitleURL(string $title_url) : string {
1082
		return "http://read.egscans.com/{$title_url}/";
1083
	}
1084
1085
	public function getChapterData(string $title_url, string $chapter) : array {
1086
		return [
1087
			'url'    => "http://read.egscans.com/{$title_url}/{$chapter}",
1088
			'number' => $chapter
1089
		];
1090
	}
1091
1092 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1093
		$titleData = [];
1094
1095
		$fullURL = $this->getFullTitleURL($title_url);
1096
		$content = $this->get_content($fullURL);
1097
1098
		$data = $this->parseTitleDataDOM(
1099
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 1096 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1100
			$title_url,
1101
			"//select[@name='manga']/option[@selected='selected']",
1102
			"//select[@name='chapter']/option[last()]",
1103
			"//html", //FIXME: EGScans doesn't have a proper title page so we can't grab chapter time.
1104
			"",
1105
			"Select a manga title to get started!"
1106
		);
1107
		if($data) {
1108
			$titleData['title'] = html_entity_decode($data['nodes_title']->textContent);
1109
1110
			$titleData['latest_chapter'] = (string) $data['nodes_chapter']->getAttribute('value');
1111
			$titleData['last_updated'] = date("Y-m-d H:i:s", now());
1112
		}
1113
1114
		return (!empty($titleData) ? $titleData : NULL);
1115
	}
1116
}
1117
1118
/*** FoolSlide sites ***/
1119
1120 View Code Duplication
class KireiCake extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1121
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1122
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1123
1124
	public function getFullTitleURL(string $title_url) : string {
1125
		return "https://reader.kireicake.com/series/{$title_url}";
1126
	}
1127
1128
	public function getChapterData(string $title_url, string $chapter) : array {
1129
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1130
		$chapter_parts = explode('/', $chapter);
1131
		return [
1132
			'url'    => "https://reader.kireicake.com/read/{$title_url}/{$chapter}/",
1133
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1134
		];
1135
	}
1136
1137
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1138
		$fullURL = $this->getFullTitleURL($title_url);
1139
		return $this->parseFoolSlide($fullURL, $title_url);
1140
	}
1141
}
1142
1143 View Code Duplication
class SeaOtterScans extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1144
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1145
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1146
1147
	public function getFullTitleURL(string $title_url) : string {
1148
		return "https://reader.seaotterscans.com/series/{$title_url}";
1149
	}
1150
1151
	public function getChapterData(string $title_url, string $chapter) : array {
1152
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1153
		$chapter_parts = explode('/', $chapter);
1154
		return [
1155
			'url'    => "https://reader.seaotterscans.com/read/{$title_url}/{$chapter}/",
1156
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1157
		];
1158
	}
1159
1160
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1161
		$fullURL = $this->getFullTitleURL($title_url);
1162
		return $this->parseFoolSlide($fullURL, $title_url);
1163
	}
1164
}
1165
1166 View Code Duplication
class HelveticaScans extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1167
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1168
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1169
1170
	public function getFullTitleURL(string $title_url) : string {
1171
		return "http://helveticascans.com/r/series/{$title_url}";
1172
	}
1173
1174
	public function getChapterData(string $title_url, string $chapter) : array {
1175
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1176
		$chapter_parts = explode('/', $chapter);
1177
		return [
1178
			'url'    => "http://helveticascans.com/r/read/{$title_url}/{$chapter}/",
1179
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1180
		];
1181
	}
1182
1183
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1184
		$fullURL = $this->getFullTitleURL($title_url);
1185
		return $this->parseFoolSlide($fullURL, $title_url);
1186
	}
1187
}
1188
1189 View Code Duplication
class SenseScans extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1190
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1191
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1192
1193
	public function getFullTitleURL(string $title_url) : string {
1194
		return "http://reader.sensescans.com/series/{$title_url}";
1195
	}
1196
1197
	public function getChapterData(string $title_url, string $chapter) : array {
1198
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1199
		$chapter_parts = explode('/', $chapter);
1200
		return [
1201
			'url'    => "http://reader.sensescans.com/read/{$title_url}/{$chapter}/",
1202
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1203
		];
1204
	}
1205
1206
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1207
		$fullURL = $this->getFullTitleURL($title_url);
1208
		return $this->parseFoolSlide($fullURL, $title_url);
1209
	}
1210
}
1211
1212 View Code Duplication
class JaiminisBox extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1213
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1214
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1215
1216
	public function getFullTitleURL(string $title_url) : string {
1217
		return "https://jaiminisbox.com/reader/series/{$title_url}";
1218
	}
1219
1220
	public function getChapterData(string $title_url, string $chapter) : array {
1221
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1222
		$chapter_parts = explode('/', $chapter);
1223
		return [
1224
			'url'    => "https://jaiminisbox.com/reader/read/{$title_url}/{$chapter}/",
1225
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1226
		];
1227
	}
1228
1229
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1230
		$fullURL = $this->getFullTitleURL($title_url);
1231
		return $this->parseFoolSlide($fullURL, $title_url);
1232
	}
1233
}
1234
1235 View Code Duplication
class DokiFansubs extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1236
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1237
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1238
1239
	public function getFullTitleURL(string $title_url) : string {
1240
		return "https://kobato.hologfx.com/reader/series/{$title_url}";
1241
	}
1242
1243
	public function getChapterData(string $title_url, string $chapter) : array {
1244
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1245
		$chapter_parts = explode('/', $chapter);
1246
		return [
1247
			'url'    => "https://kobato.hologfx.com/reader/read/{$title_url}/{$chapter}/",
1248
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1249
		];
1250
	}
1251
1252
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1253
		$fullURL = $this->getFullTitleURL($title_url);
1254
		return $this->parseFoolSlide($fullURL, $title_url);
1255
	}
1256
}
1257
1258 View Code Duplication
class DemonicScans extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1259
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1260
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1261
1262
	public function getFullTitleURL(string $title_url) : string {
1263
		return "http://www.demonicscans.com/FoOlSlide/series/{$title_url}";
1264
	}
1265
1266
	public function getChapterData(string $title_url, string $chapter) : array {
1267
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1268
		$chapter_parts = explode('/', $chapter);
1269
		return [
1270
			'url'    => "http://www.demonicscans.com/FoOlSlide/read/{$title_url}/{$chapter}/",
1271
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1272
		];
1273
	}
1274
1275
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1276
		$fullURL = $this->getFullTitleURL($title_url);
1277
		return $this->parseFoolSlide($fullURL, $title_url);
1278
	}
1279
}
1280
1281 View Code Duplication
class DeathTollScans extends Site_Model {
0 ignored issues
show
Duplication introduced by
This class 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...
1282
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1283
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1284
1285
	public function getFullTitleURL(string $title_url) : string {
1286
		return "https://reader.deathtollscans.net/series/{$title_url}";
1287
	}
1288
1289
	public function getChapterData(string $title_url, string $chapter) : array {
1290
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1291
		$chapter_parts = explode('/', $chapter);
1292
		return [
1293
			'url'    => "https://reader.deathtollscans.net/read/{$title_url}/{$chapter}/",
1294
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1295
		];
1296
	}
1297
1298
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1299
		$fullURL = $this->getFullTitleURL($title_url);
1300
		return $this->parseFoolSlide($fullURL, $title_url);
1301
	}
1302
}
1303