Completed
Push — master ( 3094af...7a87b3 )
by Angus
08:33
created

DeathTollScans::getChapterData()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 8
Code Lines 5

Duplication

Lines 8
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 4
nop 2
dl 8
loc 8
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 12
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 11
	public function __construct() {
9 11
		parent::__construct();
10
11 11
		$this->load->database();
12
		
13 11
		$this->site = get_class($this);
14 11
	}
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 11
	final protected function get_content(string $url, string $cookie_string = "", string $cookiejar_path = "", bool $follow_redirect = FALSE, bool $isPost = FALSE, array $postFields = []) {
35 11
		$ch = curl_init();
36 11
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
37 11
		curl_setopt($ch, CURLOPT_ENCODING , "gzip");
38
		//curl_setopt($ch, CURLOPT_VERBOSE, 1);
39 11
		curl_setopt($ch, CURLOPT_HEADER, 1);
40
41 11
		if($follow_redirect)        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
42
43 11
		if(!empty($cookie_string))  curl_setopt($ch, CURLOPT_COOKIE, $cookie_string);
44 11
		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 11
		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 11
		curl_setopt($ch, CURLOPT_URL, $url);
53
54 11
		if($isPost) {
55
			curl_setopt($ch,CURLOPT_POST, count($postFields));
56
			curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($postFields));
57
		}
58
59 11
		$response = curl_exec($ch);
60 11
		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 11
		$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
67 11
		$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
68 11
		$headers     = http_parse_headers(substr($response, 0, $header_size));
69 11
		$body        = substr($response, $header_size);
70 11
		curl_close($ch);
71
72
		return [
73 11
			'headers'     => $headers,
74 11
			'status_code' => $status_code,
75 11
			'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 10
	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 10
		if(!is_array($content)) {
98
			log_message('error', "{$this->site} : {$title_url} | Failed to grab URL (See above curl error)");
99
		} else {
100 10
			$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 10
			$status_code = $content['status_code'];
102 10
			$data        = $content['body'];
103
104 10
			if(!($status_code >= 200 && $status_code < 300)) {
105
				log_message('error', "{$this->site} : {$title_url} | Bad Status Code ({$status_code})");
106 10
			} else if(empty($data)) {
107
				log_message('error', "{$this->site} : {$title_url} | Data is empty? (Status code: {$status_code})");
108 10
			} else if($failure_string !== "" && strpos($data, $failure_string) !== FALSE) {
109
				log_message('error', "{$this->site} : {$title_url} | Failure string matched");
110
			} else {
111 10
				$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 10
				$dom = new DOMDocument();
114 10
				libxml_use_internal_errors(TRUE);
115 10
				$dom->loadHTML($data);
116 10
				libxml_use_internal_errors(FALSE);
117
118 10
				$xpath = new DOMXPath($dom);
119 10
				$nodes_title = $xpath->query($node_title_string);
120 10
				$nodes_row   = $xpath->query($node_row_string);
121 10
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
122 10
					$firstRow      = $nodes_row->item(0);
123 10
					$nodes_latest  = $xpath->query($node_latest_string,  $firstRow);
124
125 10
					if($node_chapter_string !== '') {
126 9
						$nodes_chapter = $xpath->query($node_chapter_string, $firstRow);
127
					} else {
128 1
						$nodes_chapter = $nodes_row;
129
					}
130
131 10
					if($nodes_latest->length === 1 && $nodes_chapter->length === 1) {
132
						return [
133 10
							'nodes_title'   => $nodes_title->item(0),
134 10
							'nodes_latest'  => $nodes_latest->item(0),
135 10
							'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 10
	public function cleanTitleDataDOM(string $data) : string {
150 10
		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 5
	final public function parseFoolSlide(string $fullURL, string $title_url) {
155 5
		$titleData = [];
156
157 5
		if($content = $this->get_content($fullURL, "", "", FALSE, TRUE, ['adult' => 'true'])) {
158 5
			$content['body'] = preg_replace('/^[\S\s]*(<article[\S\s]*)<\/article>[\S\s]*$/', '$1', $content['body']);
159
160 5
			$data = $this->parseTitleDataDOM(
161
				$content,
162
				$title_url,
163 5
				"//div[@class='large comic']/h1[@class='title']",
164 5
				"(//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 5
				"div[@class='meta_r']",
166 5
				"div[@class='title']/a"
167
			);
168 5
			if($data) {
169 5
				$titleData['title'] = trim($data['nodes_title']->textContent);
170
171 5
				$link                        = (string) $data['nodes_chapter']->getAttribute('href');
172 5
				$titleData['latest_chapter'] = preg_replace('/.*\/read\/.*?\/(.*?)\/$/', '$1', $link);
173
174 5
				$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime((string) str_replace('.', '', explode(',', $data['nodes_latest']->nodeValue)[1])));
175
			}
176
		}
177
178 5
		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 11
	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 11
		if(!class_exists($name) || !(get_parent_class($name) === 'Site_Model')) {
191
			parent::__get($name);
192
			return FALSE;
193
		} else {
194 11
			$this->loadSite($name);
195 11
			return $this->{$name};
196
		}
197
	}
198
199 11
	private function loadSite(string $siteName) {
200 11
		$this->{$siteName} = new $siteName();
201 11
	}
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 1
	public function getFullTitleURL(string $title_url) : string {
209 1
		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 1
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
220 1
		$titleData = [];
221
222 1
		$fullURL = $this->getFullTitleURL($title_url);
223 1
		$content = $this->get_content($fullURL);
224
225 1
		$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 1
			"//meta[@property='og:title']/@content",
229 1
			"//body/div[@id='page']/div[@class='left']/div[@id='chapters']/ul[1]/li[1]",
230 1
			"div/span[@class='date']",
231 1
			"div/h3/a"
232
		);
233 1
		if($data) {
234 1
			$titleData['title'] = html_entity_decode(substr($data['nodes_title']->textContent, 0, -6));
235
236 1
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
237 1
			$chapterURLSegments = explode('/', $link);
238 1
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
239 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
240
241 1
			if($firstGet) {
242
				$this->doCustomFollow($content['body']);
243
			}
244
		}
245
246 1
		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
		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 1
		return "http://www.mangahere.co/manga/{$title_url}/";
379 1
	}
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 1
		$titleData = [];
390 1
391
		$fullURL = $this->getFullTitleURL($title_url);
392 1
		$content = $this->get_content($fullURL);
393 1
394
		$data = $this->parseTitleDataDOM(
395 1
			$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 1
			"//body/section/article/div/div[@class='manga_detail']/div[@class='detail_list']/ul[1]/li[1]",
399 1
			"span[@class='right']",
400 1
			"span[@class='left']/a",
401 1
			"<div class=\"error_text\">Sorry, the page you have requested can’t be found."
402 1
		);
403
		if($data) {
404 1
			$titleData['title'] = $data['nodes_title']->textContent;
405 1
406
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
407 1
			$chapterURLSegments = explode('/', $link);
408 1
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
409 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
410 1
		}
411
412
		return (!empty($titleData) ? $titleData : NULL);
413 1
	}
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
		return FALSE; /* FIXME: Bato.to is disabled for custom updates until we can fix https://github.com/DakuTree/manga-tracker/issues/78#issuecomment-269833624 */
531
532
		$titleDataList = [];
0 ignored issues
show
Unused Code introduced by
/* FIXME: Bato.to is dis...itleDataList = array(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
533
534
		$cookies = [
535
			"lang_option=English", //FIXME: English is forced due for now. See #78.
536
			"member_id={$this->config->item('batoto_cookie_member_id')}",
537
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
538
		];
539
		$content = $this->get_content("http://bato.to/myfollows", implode("; ", $cookies), "", TRUE);
540
		if(!is_array($content)) {
541
			log_message('error', "{$this->site} /myfollows | Failed to grab URL (See above curl error)");
542
		} else {
543
			$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...
544
			$status_code = $content['status_code'];
545
			$data        = $content['body'];
546
547
			if(!($status_code >= 200 && $status_code < 300)) {
548
				log_message('error', "{$this->site} /myfollows | Bad Status Code ({$status_code})");
549
			} else if(empty($data)) {
550
				log_message('error', "{$this->site} /myfollows | Data is empty? (Status code: {$status_code})");
551
			} else {
552
				$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
553
				$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
554
555
				$dom = new DOMDocument();
556
				libxml_use_internal_errors(TRUE);
557
				$dom->loadHTML($data);
558
				libxml_use_internal_errors(FALSE);
559
560
				$xpath      = new DOMXPath($dom);
561
				$nodes_rows = $xpath->query("//table[contains(@class, 'chapters_list')]/tbody/tr[position()>1]");
562
				if($nodes_rows->length > 0) {
563
					foreach($nodes_rows as $row) {
564
						$titleData = [];
565
566
						$nodes_title   = $xpath->query("td[2]/a[1]", $row);
567
						$nodes_chapter = $xpath->query("td[2]/a[2]", $row);
568
						$nodes_lang    = $xpath->query("td[3]/div", $row);
569
						$nodes_latest  = $xpath->query("td[5]", $row);
570
571
						if($nodes_lang->length === 1 && $nodes_lang->item(0)->getAttribute('title') == 'English') {
572
							if($nodes_title->length === 1 && $nodes_chapter->length === 1 && $nodes_latest->length === 1) {
573
								$title = $nodes_title->item(0);
574
575
								preg_match('/(?<id>[0-9]+)$/', $title->getAttribute('href'), $title_url_arr);
576
								$title_url = "{$title_url_arr['id']}:--:English"; //FIXME: English is currently forced, see #78
577
578
								if(!array_key_exists($title_url, $titleDataList)) {
579
									$titleData['title'] = trim($title->textContent);
580
581
									$chapter = $nodes_chapter->item(0);
582
									preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($chapter->nodeValue), $text);
583
									$titleData['latest_chapter'] = substr($chapter->getAttribute('href'), 8) . ':--:' . ((!empty($text['volume']) ? 'v' . $text['volume'] . '/' : '') . 'c' . $text['chapter'] . (!empty($text['extra']) ? '-' . $text['extra'] : ''));
584
585
									$dateString = $nodes_latest->item(0)->nodeValue;
586
									if($dateString == 'An hour ago') {
587
										$dateString = '1 hour ago';
588
									}
589
									$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
590
591
592
									$titleDataList[$title_url] = $titleData;
593
								}
594
							} else {
595
								log_message('error', "{$this->site}/Custom | Invalid amount of nodes (TITLE: {$nodes_title->length} | CHAPTER: {$nodes_chapter->length}) | LATEST: {$nodes_latest->length})");
596
							}
597
						}
598
					}
599
				} else {
600
					log_message('error', '{$this->site} | Following list is empty?');
601
				}
602
			}
603
		}
604
		return $titleDataList;
605
	}
606
}
607
608
class DynastyScans extends Site_Model {
609
	//FIXME: This has some major issues. SEE: https://github.com/DakuTree/manga-tracker/issues/58
610
611
	public $titleFormat   = '/^[a-z0-9_]+:--:(?:0|1)$/';
612
	public $chapterFormat = '/^[0-9a-z_]+$/';
613
614
	public function getFullTitleURL(string $title_string) : string {
615 1
		$title_parts = explode(':--:', $title_string);
616 1
		$url_type = ($title_parts[1] == '0' ? 'series' : 'chapters');
617 1
618
		return 'http://dynasty-scans.com/'.$url_type.'/'.$title_parts[0];
619 1
	}
620
621
	public function getChapterData(string $title_string, string $chapter) : array {
622
		$title_parts = explode(':--:', $title_string);
623
		/* Known chapter url formats (# is numbers):
624
		       chapters_#A_#B - Ch#A-#B
625
		       ch_#A          - Ch#A
626
		       ch_#A_#B       - Ch#A.#B
627
		       <NOTHING>      - Oneshot (This is passed as "oneshot")
628
		*/
629
630
		$chapterData = [
631
			'url'    => 'http://dynasty-scans.com/chapters/' . $title_parts[0].'_'.$chapter,
632
			'number' => ''
633
		];
634
635
		if($chapter == 'oneshot') {
636
			$chapterData['number'] = 'oneshot';
637
		} else {
638
			$chapter = preg_replace("/^([a-zA-Z]+)/", '$1_', $chapter);
639
			$chapterSegments = explode('_', $chapter);
640
			switch($chapterSegments[0]) {
641
				case 'ch':
642
					$chapterData['number'] = 'c'.$chapterSegments[1].(isset($chapterSegments[2]) && !empty($chapterSegments[2]) ? '.'.$chapterSegments[2] : '');
643
					break;
644
645
				case 'chapters':
646
					//This is barely ever used, but I have seen it.
647
					$chapterData['number'] = 'c'.$chapterSegments[1].'-'.$chapterSegments[2];
648
					break;
649
650
				default:
651
					//TODO: FALLBACK, ALERT ADMIN?
652
					$chapterData['number'] = $chapter;
653
					break;
654
			}
655
		}
656
		return $chapterData;
657
	}
658
659
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
660 1
		$titleData = [];
661 1
662
		$fullURL = $this->getFullTitleURL($title_url);
663 1
		$content = $this->get_content($fullURL);
664 1
665
		$title_parts = explode(':--:', $title_url);
666 1
		switch($title_parts[1]) {
667 1
			case '0':
668 1
				//Normal series.
669
				$data = $this->parseTitleDataDOM(
670 1
					$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 663 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...
671
					$title_url,
672
					"//h2[@class='tag-title']/b[1]",
673 1
					"(//dl[@class='chapter-list']/dd[a[contains(@href,'/chapters/')]])[last()]",
674 1
					"small",
675 1
					"a[@class='name']"
676 1
				);
677
				if($data) {
678 1
					$titleData['title'] = $data['nodes_title']->textContent;
679 1
					//In cases where the series is a doujin, try and prepend the copyright.
680
					preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $content['body'], $matchesD);
681 1
					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...
682 1
						$titleData['title'] = substr($matchesD[1], 0, -7).' - '.$titleData['title'];
683
					}
684
685
					$chapterURLSegments = explode('/', (string) $data['nodes_chapter']->getAttribute('href'));
686 1
					if (strpos($chapterURLSegments[2], $title_parts[0]) !== false) {
687 1
						$titleData['latest_chapter'] = substr($chapterURLSegments[2], strlen($title_parts[0]) + 1);
688 1
					} else {
689
						$titleData['latest_chapter'] = $chapterURLSegments[2];
690
					}
691
692
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime(str_replace("'", '', substr((string) $data['nodes_latest']->textContent, 9))));
693 1
				}
694
				break;
695 1
696
			case '1':
697
				//Oneshot.
698
				$data = $content['body'];
699
700
				preg_match('/<b>.*<\/b>/', $data, $matchesT);
701
				preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $data, $matchesD);
702
				$titleData['title'] = (!empty($matchesD) ? ($matchesD[1] !== 'Original' ? $matchesD[1].' - ' : '') : '') . substr($matchesT[0], 3, -4);
703
704
				$titleData['latest_chapter'] = 'oneshot'; //This will never change
705
706
				preg_match('/<i class="icon-calendar"><\/i> (.*)<\/span>/', $data, $matches);
707
				$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime($matches[1]));
708
709
				//Oneshots are special, and really shouldn't need to be re-tracked
710
				$titleData['status'] = '2';
711
				break;
712
713
			default:
714
				//something went wrong
715
				break;
716
		}
717
		return (!empty($titleData) ? $titleData : NULL);
718 1
	}
719
}
720
721
class MangaPanda extends Site_Model {
722
	//NOTE: MangaPanda has manga pages under the root URL, so we need to filter out pages we know that aren't manga.
723
	public $titleFormat   = '/^(?!(?:latest|search|popular|random|alphabetical|privacy)$)([a-z0-9-]+)$/';
724
	public $chapterFormat = '/^[0-9]+$/';
725
726
	public function getFullTitleURL(string $title_url) : string {
727 1
		return "http://www.mangapanda.com/{$title_url}";
728 1
	}
729
730
	public function getChapterData(string $title_url, string $chapter) : array {
731
		return [
732
			'url'    => "http://www.mangapanda.com/{$title_url}/{$chapter}/",
733
			'number' => 'c'.$chapter
734
		];
735
	}
736
737 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
738 1
		$titleData = [];
739 1
740
		$fullURL = $this->getFullTitleURL($title_url);
741 1
		$content = $this->get_content($fullURL);
742 1
743
		$data = $this->parseTitleDataDOM(
744 1
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 741 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...
745
			$title_url,
746
			"//h2[@class='aname']",
747 1
			"(//table[@id='listing']/tr)[last()]",
748 1
			"td[2]",
749 1
			"td[1]/a"
750 1
		);
751
		if($data) {
752 1
			$titleData['title'] = $data['nodes_title']->textContent;
753 1
754
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
755 1
756
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
757 1
		}
758
759
		return (!empty($titleData) ? $titleData : NULL);
760 1
	}
761
}
762
763
class MangaStream extends Site_Model {
764
	public $titleFormat   = '/^[a-z0-9_]+$/';
765
	public $chapterFormat = '/^(.*?)\/[0-9]+$/';
766
767
	public function getFullTitleURL(string $title_url) : string {
768
		return "https://mangastream.com/manga/{$title_url}/";
769
	}
770
771
	public function getChapterData(string $title_url, string $chapter) : array {
772
		return [
773
			'url'    => "https://mangastream.com/r/{$title_url}/{$chapter}",
774
			'number' => 'c'.explode('/', $chapter)[0]
775
		];
776
	}
777
778 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
779
		$titleData = [];
780
781
		$fullURL = $this->getFullTitleURL($title_url);
782
		$content = $this->get_content($fullURL);
783
784
		$data = $this->parseTitleDataDOM(
785
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 782 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...
786
			$title_url,
787
			"//div[contains(@class, 'content')]/div[1]/h1",
788
			"//div[contains(@class, 'content')]/div[1]/table/tr[2]",
789
			"td[2]",
790
			"td[1]/a",
791
			"<h1>Page Not Found</h1>"
792
		);
793
		if($data) {
794
			$titleData['title'] = $data['nodes_title']->textContent;
795
796
			$titleData['latest_chapter'] = preg_replace('/^.*\/(.*?\/[0-9]+)\/[0-9]+$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
797
798
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
799
		}
800
801
		return (!empty($titleData) ? $titleData : NULL);
802
	}
803
}
804
805
class WebToons extends Site_Model {
806
	/* Webtoons.com has a very weird and pointless URL format.
807
	   TITLE URL:   /#LANG#/#GENRE#/#TITLE#/list?title_no=#TITLEID#
808
	   RSS URL:     /#LANG#/#GENRE#/#TITLE#/rss?title_no=#TITLEID#
809
	   CHAPTER URL: /#LANG#/#GENRE#/#TITLE#/#CHAPTER#/viewer?title_no=#TITLEID#&episode_no=#CHAPTERID#
810
811
	   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).
812
	   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.
813
	   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.
814
815
	   FORMATS:
816
	   TITLE_URL: ID:--:LANG:--:TITLE:--:GENRE
817
	   CHAPTER:   ID:--:CHAPTER_N
818
	*/
819
	//private $validLang = ['en', 'zh-hant', 'zh-hans', 'th', 'id'];
820
821
	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)$/';
822
	public $chapterFormat = '/^[0-9]+:--:.*$/';
823
824
	public function getFullTitleURL(string $title_url) : string {
825
		$title_parts = explode(':--:', $title_url);
826
		return "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/list?title_no={$title_parts[0]}/";
827
	}
828
829
	public function getChapterData(string $title_url, string $chapter) : array {
830
		$title_parts   = explode(':--:', $title_url);
831
		$chapter_parts = explode(':--:', $chapter);
832
833
		return [
834
			'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]}",
835
			'number' => $chapter_parts[1] //TODO: Possibly replace certain formats in here? Since webtoons doesn't have a standard chapter format
836
		];
837
	}
838
839
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
840 1
		$titleData = [];
841 1
842
		//FIXME: We don't use parseTitleDOM here due to using rss. Should probably have an alternate method for XML parsing.
843
844
		//NOTE: getTitleData uses a different FullTitleURL due to it grabbing the rss ver. instead.
845
		$title_parts = explode(':--:', $title_url);
846 1
		$fullURL = "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/rss?title_no={$title_parts[0]}";
847 1
848
		$content = $this->get_content($fullURL);
849 1
		$data = $content['body'];
850 1
		if($data !== 'Can\'t find the manga series.') { //FIXME: We should check for he proper error here.
851 1
			$xml = simplexml_load_string($data) or die("Error: Cannot create object");
852 1
			if(isset($xml->{'channel'}->item[0])) {
853 1
				$titleData['title'] = trim((string) $xml->{'channel'}->title);
854 1
855
				$chapterURLSegments = explode('/', ((string) $xml->{'channel'}->item[0]->link));
856 1
				$titleData['latest_chapter'] = preg_replace('/^.*?([0-9]+)$/', '$1', $chapterURLSegments[7]) . ':--:' . $chapterURLSegments[6];
857 1
				$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $xml->{'channel'}->item[0]->pubDate));
858 1
			}
859
		} else {
860
			log_message('error', "Series missing? (WebToons): {$title_url}");
861
			return NULL;
862
		}
863
864
		return (!empty($titleData) ? $titleData : NULL);
865 1
	}
866
}
867
868
class KissManga extends Site_Model {
869
	/* 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.
870
	   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.
871
872
	   To generate this cookie, we need three variables. Two are static, but the other is generated by randomly generated JS on the page.
873
	   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.
874
	   The other option is using regex, but that is a rabbit hole I don't want to touch with a ten-foot pole.
875
876
	   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.
877
	   The cookie has a length of 1 year, but I don't think it actually lasts that long, so we update every 6hours instead.
878
	   I should probably also mention that the cookie generated also uses your user-agent, so if it changes the cookie will break.
879
	*/
880
881
	public $titleFormat   = '/^[A-Za-z0-9-]+$/';
882
	public $chapterFormat = '/^.*?:--:[0-9]+$/';
883
884
	public function getFullTitleURL(string $title_url) : string {
885
		return "http://kissmanga.com/Manga/{$title_url}";
886
	}
887
888 View Code Duplication
	public function getChapterData(string $title_url, string $chapter) : array {
889
		$chapter_parts = explode(':--:', $chapter);
890
891
		return [
892
			'url'    => "http://kissmanga.com/Manga/{$title_url}/{$chapter_parts[0]}?id={$chapter_parts[1]}",
893
			//FIXME: KM has an extremely inconsistant chapter format which makes it difficult to parse.
894
			'number' => /*preg_replace('/--.*?$/', '', */$chapter_parts[0]/*)*/
895
		];
896
	}
897
898
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
899
		$titleData = [];
900
901
		//Check if cookiejar is a day old (so we can know if something went wrong)
902
		$cookiejar_path = str_replace("public/", "_scripts/cookiejar", FCPATH);
903
		$cookie_last_updated = filemtime($cookiejar_path);
904
		if($cookie_last_updated && ((time() - 86400) < $cookie_last_updated)) {
905
906
			$fullURL = $this->getFullTitleURL($title_url);
907
908
			$content = $this->get_content($fullURL, '', $cookiejar_path);
909
			$data = $content['body'];
910
			if(strpos($data, 'containerRoot') !== FALSE) {
911
				//FIXME: For whatever reason, we can't grab the entire div without simplexml shouting at us
912
				$data = preg_replace('/^[\S\s]*(<div id="leftside">[\S\s]*)<div id="rightside">[\S\s]*$/', '$1', $data);
913
914
				$dom = new DOMDocument();
915
				libxml_use_internal_errors(true);
916
				$dom->loadHTML($data);
917
				libxml_use_internal_errors(false);
918
919
				$xpath = new DOMXPath($dom);
920
921
				$nodes_title = $xpath->query("//a[@class='bigChar']");
922
				$nodes_row   = $xpath->query("//table[@class='listing']/tr[3]");
923
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
924
					$titleData['title'] = $nodes_title->item(0)->textContent;
925
926
					$firstRow      = $nodes_row->item(0);
927
					$nodes_latest  = $xpath->query("td[2]",   $firstRow);
928
					$nodes_chapter = $xpath->query("td[1]/a", $firstRow);
929
930
					$link = (string) $nodes_chapter->item(0)->getAttribute('href');
931
					$chapterURLSegments = explode('/', preg_replace('/\?.*$/', '', $link));
932
					$titleData['latest_chapter'] = $chapterURLSegments[3] . ':--:' . preg_replace('/.*?([0-9]+)$/', '$1', $link);
933
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $nodes_latest->item(0)->textContent));
934
				}
935
			} 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...
936
				//TODO: Throw ERRORS;
937
			}
938
		} 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...
939
			//Do nothing, wait until next update.
940
			//TODO: NAG ADMIN??
941
		}
942
943
		return (!empty($titleData) ? $titleData : NULL);
944
	}
945
}
946
947
class GameOfScanlation extends Site_Model {
948
	public $titleFormat   = '/^[a-z0-9\.-]+$/';
949
	public $chapterFormat = '/^[a-z0-9\.-]+$/';
950
951
	public function getFullTitleURL(string $title_url) : string {
952
		/* NOTE: GoS is a bit weird in that it has two separate title URL formats. One uses /projects/ and the other uses /fourms/.
953
		         The bad thing is these are interchangeable, despite them showing the exact same listing page.
954
		         Thankfully the title_url of manga which use /forums/ seem to be appended with ".%ID%" which means we can easily check them. */
955
956
		if (strpos($title_url, '.') !== FALSE) {
957
			$format = "https://gameofscanlation.moe/forums/{$title_url}/";
958
		} else {
959
			$format = "https://gameofscanlation.moe/projects/{$title_url}/";
960
		}
961
		return $format;
962
	}
963
964
	public function getChapterData(string $title_url, string $chapter) : array {
965
		return [
966
			'url'    => "https://gameofscanlation.moe/projects/".preg_replace("/\\.[0-9]+$/", "", $title_url).'/'.$chapter.'/',
967
			'number' => preg_replace("/chapter-/", "c", preg_replace("/\\.[0-9]+$/", "", $chapter))
968
		];
969
	}
970
971 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
972
		$titleData = [];
973
974
		$fullURL = $this->getFullTitleURL($title_url);
975
976
		$content = $this->get_content($fullURL);
977
978
		$data = $this->parseTitleDataDOM(
979
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 976 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...
980
			$title_url,
981
			"//meta[@property='og:title']",
982
			"//ol[@class='discussionListItems']/li[1]/div[@class='home_list']/ul/li/div[@class='list_press_text']",
983
			"p[@class='author']/span|p[@class='author']/abbr",
984
			"p[@class='text_work']/a"
985
		);
986
		if($data) {
987
			$titleData['title'] = trim(html_entity_decode($data['nodes_title']->getAttribute('content')));
988
989
			$titleData['latest_chapter'] = preg_replace('/^projects\/.*?\/(.*?)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
990
991
			$titleData['last_updated'] =  date("Y-m-d H:i:s",(int) $data['nodes_latest']->getAttribute('title'));
992
		}
993
994
		return (!empty($titleData) ? $titleData : NULL);
995
	}
996
}
997
998
class MangaCow extends Site_Model {
999
	public $titleFormat   = '/^[a-zA-Z0-9_]+$/';
1000
	public $chapterFormat = '/^[0-9]+$/';
1001
1002
	public function getFullTitleURL(string $title_url) : string {
1003 1
		return "http://mngcow.co/{$title_url}/";
1004 1
	}
1005
1006
	public function getChapterData(string $title_url, string $chapter) : array {
1007
		return [
1008
			'url'    => $this->getFullTitleURL($title_url).$chapter.'/',
1009
			'number' => "c{$chapter}"
1010
		];
1011
	}
1012
1013 View Code Duplication
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1014 1
		$titleData = [];
1015 1
1016
		$fullURL = $this->getFullTitleURL($title_url);
1017 1
1018
		$content = $this->get_content($fullURL);
1019 1
1020
		$data = $this->parseTitleDataDOM(
1021 1
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 1018 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...
1022
			$title_url,
1023
			"//h4",
1024 1
			"//ul[contains(@class, 'mng_chp')]/li[1]/a[1]",
1025 1
			"b[@class='dte']",
1026 1
			"",
1027 1
			"404 Page Not Found"
1028 1
		);
1029
		if($data) {
1030 1
			$titleData['title'] = trim($data['nodes_title']->textContent);
1031 1
1032
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
1033 1
1034
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) substr($data['nodes_latest']->getAttribute('title'), 13)));
1035 1
		}
1036
1037
		return (!empty($titleData) ? $titleData : NULL);
1038 1
	}
1039
}
1040
1041
class EGScans extends Site_Model {
1042
	public $titleFormat   = '/^[A-Za-z0-9\-_\!,]+$/';
1043
	public $chapterFormat = '/^Chapter_[0-9]+(?:_extra)?$/';
1044
1045
	public function getFullTitleURL(string $title_url) : string {
1046
		return "http://read.egscans.com/{$title_url}/";
1047
	}
1048 1
1049 1
	public function getChapterData(string $title_url, string $chapter) : array {
1050
		return [
1051
			'url'    => "http://read.egscans.com/{$title_url}/{$chapter}",
1052
			'number' => $chapter
1053
		];
1054
	}
1055
1056
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1057
		$titleData = [];
1058
1059
		$fullURL = $this->getFullTitleURL($title_url);
1060
		$content = $this->get_content($fullURL);
1061 1
1062 1
		$data = $this->parseTitleDataDOM(
1063 1
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 1060 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...
1064
			$title_url,
1065
			"//select[@name='manga']/option[@selected]",
1066
			"//select[@name='chapter']/option[last()]",
1067
			"//html", //FIXME: EGScans doesn't have a proper title page so we can't grab chapter time.
1068
			"",
1069
			"Select a manga title to get started!"
1070
		);
1071 1
		if($data) {
1072 1
			$titleData['title'] = html_entity_decode($data['nodes_title']->getAttribute('value'));
1073
1074
			$titleData['latest_chapter'] = (string) $data['nodes_chapter']->getAttribute('value');
1075
			$titleData['last_updated'] = now();
1076
		}
1077
1078
		return (!empty($titleData) ? $titleData : NULL);
1079
	}
1080
}
1081
1082
/*** FoolSlide sites ***/
1083
1084 1 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...
1085 1
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1086 1
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1087
1088
	public function getFullTitleURL(string $title_url) : string {
1089
		return "https://reader.kireicake.com/series/{$title_url}";
1090
	}
1091
1092
	public function getChapterData(string $title_url, string $chapter) : array {
1093
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1094 1
		$chapter_parts = explode('/', $chapter);
1095 1
		return [
1096
			'url'    => "https://reader.kireicake.com/read/{$title_url}/{$chapter}/",
1097
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1098
		];
1099
	}
1100
1101
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1102
		$fullURL = $this->getFullTitleURL($title_url);
1103
		return $this->parseFoolSlide($fullURL, $title_url);
1104
	}
1105
}
1106
1107 1 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...
1108 1
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1109 1
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1110
1111
	public function getFullTitleURL(string $title_url) : string {
1112
		return "https://reader.seaotterscans.com/series/{$title_url}";
1113
	}
1114
1115
	public function getChapterData(string $title_url, string $chapter) : array {
1116
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1117 1
		$chapter_parts = explode('/', $chapter);
1118 1
		return [
1119
			'url'    => "https://reader.seaotterscans.com/read/{$title_url}/{$chapter}/",
1120
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1121
		];
1122
	}
1123
1124
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1125
		$fullURL = $this->getFullTitleURL($title_url);
1126
		return $this->parseFoolSlide($fullURL, $title_url);
1127
	}
1128
}
1129
1130 1 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...
1131 1
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1132 1
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1133
1134
	public function getFullTitleURL(string $title_url) : string {
1135
		return "http://helveticascans.com/reader/series/{$title_url}";
1136
	}
1137
1138
	public function getChapterData(string $title_url, string $chapter) : array {
1139
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1140
		$chapter_parts = explode('/', $chapter);
1141
		return [
1142
			'url'    => "http://helveticascans.com/reader/read/{$title_url}/{$chapter}/",
1143
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1144
		];
1145
	}
1146
1147
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1148
		$fullURL = $this->getFullTitleURL($title_url);
1149
		return $this->parseFoolSlide($fullURL, $title_url);
1150
	}
1151
}
1152
1153 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...
1154
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1155
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1156
1157
	public function getFullTitleURL(string $title_url) : string {
1158
		return "http://reader.sensescans.com/series/{$title_url}";
1159
	}
1160
1161
	public function getChapterData(string $title_url, string $chapter) : array {
1162
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1163
		$chapter_parts = explode('/', $chapter);
1164
		return [
1165
			'url'    => "http://reader.sensescans.com/read/{$title_url}/{$chapter}/",
1166
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1167
		];
1168
	}
1169
1170
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1171
		$fullURL = $this->getFullTitleURL($title_url);
1172
		return $this->parseFoolSlide($fullURL, $title_url);
1173
	}
1174
}
1175
1176 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...
1177
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1178
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1179
1180
	public function getFullTitleURL(string $title_url) : string {
1181
		return "https://jaiminisbox.com/reader/series/{$title_url}";
1182
	}
1183
1184
	public function getChapterData(string $title_url, string $chapter) : array {
1185
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1186 1
		$chapter_parts = explode('/', $chapter);
1187 1
		return [
1188
			'url'    => "https://jaiminisbox.com/reader/read/{$title_url}/{$chapter}/",
1189
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1190
		];
1191
	}
1192
1193
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1194
		$fullURL = $this->getFullTitleURL($title_url);
1195
		return $this->parseFoolSlide($fullURL, $title_url);
1196
	}
1197
}
1198
1199 1 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...
1200 1
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1201 1
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1202
1203
	public function getFullTitleURL(string $title_url) : string {
1204
		return "https://kobato.hologfx.com/reader/series/{$title_url}";
1205
	}
1206
1207
	public function getChapterData(string $title_url, string $chapter) : array {
1208
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1209
		$chapter_parts = explode('/', $chapter);
1210
		return [
1211
			'url'    => "https://kobato.hologfx.com/reader/read/{$title_url}/{$chapter}/",
1212
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1213
		];
1214
	}
1215
1216
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1217
		$fullURL = $this->getFullTitleURL($title_url);
1218
		return $this->parseFoolSlide($fullURL, $title_url);
1219
	}
1220
}
1221
1222 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...
1223
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1224
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1225
1226
	public function getFullTitleURL(string $title_url) : string {
1227
		return "http://www.demonicscans.com/FoOlSlide/series/{$title_url}";
1228
	}
1229
1230
	public function getChapterData(string $title_url, string $chapter) : array {
1231
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1232
		$chapter_parts = explode('/', $chapter);
1233
		return [
1234
			'url'    => "http://www.demonicscans.com/FoOlSlide/read/{$title_url}/{$chapter}/",
1235
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1236
		];
1237
	}
1238
1239
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1240
		$fullURL = $this->getFullTitleURL($title_url);
1241
		return $this->parseFoolSlide($fullURL, $title_url);
1242
	}
1243
}
1244
1245 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...
1246
	public $titleFormat   = '/^[a-z0-9_-]+$/';
1247
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
1248
1249
	public function getFullTitleURL(string $title_url) : string {
1250
		return "https://reader.deathtollscans.net/series/{$title_url}";
1251
	}
1252
1253
	public function getChapterData(string $title_url, string $chapter) : array {
1254
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
1255
		$chapter_parts = explode('/', $chapter);
1256
		return [
1257
			'url'    => "https://reader.deathtollscans.net/read/{$title_url}/{$chapter}/",
1258
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
1259
		];
1260
	}
1261
1262
	public function getTitleData(string $title_url, bool $firstGet = FALSE) {
1263
		$fullURL = $this->getFullTitleURL($title_url);
1264
		return $this->parseFoolSlide($fullURL, $title_url);
1265
	}
1266
}
1267