Completed
Push — master ( d52c51...f61f1c )
by Angus
03:30
created

MangaFox::getFullTitleURL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
abstract class Site_Model extends CI_Model {
4
	public $site          = '';
5
	public $titleFormat   = '';
6
	public $chapterFormat = '';
7
8 121
	public function __construct() {
9 121
		parent::__construct();
10
11 121
		$this->load->database();
12 121
	}
13
14
	abstract public function getFullTitleURL(string $title_url) : string;
15
16
	abstract public function getChapterData(string $title_url, string $chapter) : array;
17
18
	//TODO: When ci-phpunit-test supports PHP Parser 3.x, add " : ?array"
19
	abstract public function getTitleData(string $title_url);
20
21
	public function isValidTitleURL(string $title_url) : bool {
22
		$success = (bool) preg_match($this->titleFormat, $title_url);
23
		if(!$success) log_message('error', "Invalid Title URL ({$this->site}): {$title_url}");
24
		return $success;
25
	}
26
	public function isValidChapter(string $chapter) : bool {
27
		$success = (bool) preg_match($this->chapterFormat, $chapter);
28
		if(!$success) log_message('error', "Invalid Chapter ({$this->site}): {$chapter}");
29
		return $success;
30
	}
31
32 19
	final protected function get_content(string $url, string $cookie_string = "", string $cookiejar_path = "", bool $follow_redirect = FALSE) {
33 19
		$ch = curl_init();
34 19
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
35 19
		curl_setopt($ch, CURLOPT_ENCODING , "gzip");
36
		//curl_setopt($ch, CURLOPT_VERBOSE, 1);
37 19
		curl_setopt($ch, CURLOPT_HEADER, 1);
38
39 19
		if($follow_redirect)        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
40
41 19
		if(!empty($cookie_string))  curl_setopt($ch, CURLOPT_COOKIE, $cookie_string);
42 19
		if(!empty($cookiejar_path)) curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiejar_path);
43
44
		//Some sites check the useragent for stuff, use a pre-defined user-agent to avoid stuff.
45 19
		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');
46
47
		//TODO: Check in a while if this being enabled still causes issues
48
		//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); //FIXME: This isn't safe, but it allows us to grab SSL URLs
49
50 19
		curl_setopt($ch, CURLOPT_URL, $url);
51 19
		$response = curl_exec($ch);
52 19
		if($response === FALSE) {
53
			log_message('error', "curl failed with error: ".curl_errno($ch)." | ".curl_error($ch));
54
			//FIXME: We don't always account for FALSE return
55
			return FALSE;
56
		}
57
58 19
		$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
59 19
		$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
60 19
		$headers     = http_parse_headers(substr($response, 0, $header_size));
61 19
		$body        = substr($response, $header_size);
62 19
		curl_close($ch);
63
64
		return [
65 19
			'headers'     => $headers,
66 19
			'status_code' => $status_code,
67 19
			'body'        => $body
68
		];
69
	}
70
71
	/**
72
	 * @param array  $content
73
	 * @param string $title_url
74
	 * @param string $node_title_string
75
	 * @param string $node_row_string
76
	 * @param string $node_latest_string
77
	 * @param string $node_chapter_string
78
	 * @param string $failure_string
79
	 *
80
	 * @return DOMElement[]|false
81
	 */
82 18
	final protected function parseTitleDataDOM(
83
		$content, string $title_url,
84
		string $node_title_string, string $node_row_string,
85
		string $node_latest_string, string $node_chapter_string,
86
		string $failure_string = "") {
87
		//list('headers' => $headers, 'status_code' => $status_code, 'body' => $data) = $content; //TODO: PHP 7.1
88
89 18
		if(!is_array($content)) {
90
			log_message('error', "{$this->site} : {$title_url} | Failed to grab URL (See above curl error)");
91
		} else {
92 18
			$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...
93 18
			$status_code = $content['status_code'];
94 18
			$data        = $content['body'];
95
96 18
			if(!($status_code >= 200 && $status_code < 300)) {
97 8
				log_message('error', "{$this->site} : {$title_url} | Bad Status Code ({$status_code})");
98 10
			} else if(empty($data)) {
99
				log_message('error', "{$this->site} : {$title_url} | Data is empty? (Status code: {$status_code})");
100 10
			} else if($failure_string !== "" && strpos($data, $failure_string) !== FALSE) {
101 1
				log_message('error', "{$this->site} : {$title_url} | Failure string matched");
102
			} else {
103 9
				$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.
104
105 9
				$dom = new DOMDocument();
106 9
				libxml_use_internal_errors(TRUE);
107 9
				$dom->loadHTML($data);
108 9
				libxml_use_internal_errors(FALSE);
109
110 9
				$xpath = new DOMXPath($dom);
111 9
				$nodes_title = $xpath->query($node_title_string);
112 9
				$nodes_row   = $xpath->query($node_row_string);
113 9
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
114 9
					$firstRow      = $nodes_row->item(0);
115 9
					$nodes_latest  = $xpath->query($node_latest_string,  $firstRow);
116
117 9
					if($node_chapter_string !== '') {
118 8
						$nodes_chapter = $xpath->query($node_chapter_string, $firstRow);
119
					} else {
120 1
						$nodes_chapter = $nodes_row;
121
					}
122
123 9
					if($nodes_latest->length === 1 && $nodes_chapter->length === 1) {
124
						return [
125 9
							'nodes_title'   => $nodes_title->item(0),
126 9
							'nodes_latest'  => $nodes_latest->item(0),
127 9
							'nodes_chapter' => $nodes_chapter->item(0)
128
						];
129
					} else {
130
						log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (LATEST: {$nodes_latest->length} | CHAPTER: {$nodes_chapter->length})");
131
					}
132
				} else {
133
					log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (TITLE: {$nodes_title->length} | ROW: {$nodes_row->length})");
134
				}
135
			}
136
		}
137
138 9
		return FALSE;
139
	}
140
141 9
	public function cleanTitleDataDOM(string $data) : string {
142 9
		return $data;
143
	}
144
145
	//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.
146 8
	final public function parseFoolSlide(string $fullURL, string $title_url) {
147 8
		$titleData = [];
148
149 8
		if($content = $this->get_content($fullURL)) {
150 8
			$content['body'] = preg_replace('/^[\S\s]*(<article[\S\s]*)<\/article>[\S\s]*$/', '$1', $content['body']);
151
152 8
			$data = $this->parseTitleDataDOM(
153
				$content,
154
				$title_url,
155 8
				"//div[@class='large comic']/h1[@class='title']",
156 8
				"(//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]",
157 8
				"div[@class='meta_r']",
158 8
				"div[@class='title']/a"
159
			);
160 8
			if($data) {
161 4
				$titleData['title'] = trim($data['nodes_title']->textContent);
162
163 4
				$link                        = (string) $data['nodes_chapter']->getAttribute('href');
164 4
				$titleData['latest_chapter'] = preg_replace('/.*\/read\/.*?\/(.*?)\/$/', '$1', $link);
165
166 4
				$titleData['last_updated'] = date("Y-m-d H:i:s", strtotime((string) str_replace('.', '', explode(',', $data['nodes_latest']->nodeValue)[1])));
167
			}
168
		}
169
170 8
		return (!empty($titleData) ? $titleData : NULL);
171
	}
172
}
173
class Sites_Model extends CI_Model {
174
	//FIXME: Is it possible to automatically generate this in some way or another?
175
	public $MangaFox;
176
	public $MangaHere;
177
	public $Batoto;
178
	public $DynastyScans;
179
	public $MangaPanda;
180
	public $MangaStream;
181
	public $WebToons;
182
	public $KissManga;
183
	public $KireiCake;
184
	public $GameOfScanlation;
185
	public $MangaCow;
186
	public $SeaOtterScans;
187
	public $HelveticaScans;
188
	public $SenseScans;
189
	public $JaiminisBox;
190
191 121
	public function __construct() {
192 121
		parent::__construct();
193
194 121
		$this->MangaFox         = new MangaFox();
195 121
		$this->MangaHere        = new MangaHere();
196 121
		$this->Batoto           = new Batoto();
197 121
		$this->DynastyScans     = new DynastyScans();
198 121
		$this->MangaPanda       = new MangaPanda();
199 121
		$this->MangaStream      = new MangaStream();
200 121
		$this->WebToons         = new WebToons();
201 121
		$this->KissManga        = new KissManga();
202 121
		$this->KireiCake        = new KireiCake();
203 121
		$this->GameOfScanlation = new GameOfScanlation();
204 121
		$this->MangaCow         = new MangaCow();
205 121
		$this->SeaOtterScans    = new SeaOtterScans();
206 121
		$this->HelveticaScans   = new HelveticaScans();
207 121
		$this->SenseScans       = new SenseScans();
208 121
		$this->JaiminisBox      = new JaiminisBox();
209 121
	}
210
}
211
212
class MangaFox extends Site_Model {
213
	public $site          = 'MangaFox';
214
	public $titleFormat   = '/^[a-z0-9_]+$/';
215
	public $chapterFormat = '/^(?:v[0-9a-zA-Z]+\/)?c[0-9\.]+$/';
216
217 2
	public function getFullTitleURL(string $title_url) : string {
218 2
		return "http://mangafox.me/manga/{$title_url}/";
219
	}
220
221
	public function getChapterData(string $title_url, string $chapter) : array {
222
		return [
223
			'url'    => "http://mangafox.me/manga/{$title_url}/{$chapter}/",
224
			'number' => $chapter
225
		];
226
	}
227
228 2
	public function getTitleData(string $title_url) {
229 2
		$titleData = [];
230
231 2
		$fullURL = $this->getFullTitleURL($title_url);
232 2
		$content = $this->get_content($fullURL);
233
234 2
		$data = $this->parseTitleDataDOM(
235
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 232 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...
236
			$title_url,
237 2
			"//meta[@property='og:title']/@content",
238 2
			"//body/div[@id='page']/div[@class='left']/div[@id='chapters']/ul[1]/li[1]",
239 2
			"div/span[@class='date']",
240 2
			"div/h3/a"
241
		);
242 2
		if($data) {
243 1
			$titleData['title'] = html_entity_decode(substr($data['nodes_title']->textContent, 0, -6));
244
245 1
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
246 1
			$chapterURLSegments = explode('/', $link);
247 1
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
248 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
249
		}
250
251 2
		return (!empty($titleData) ? $titleData : NULL);
252
	}
253
}
254
255
class MangaHere extends Site_Model {
256
	public $site          = 'MangaHere';
257
	public $titleFormat   = '/^[a-z0-9_]+$/';
258
	public $chapterFormat = '/^(?:v[0-9]+\/)?c[0-9]+(?:\.[0-9]+)?$/';
259
260 2
	public function getFullTitleURL(string $title_url) : string {
261 2
		return "http://www.mangahere.co/manga/{$title_url}/";
262
	}
263
264
	public function getChapterData(string $title, string $chapter) : array {
265
		return [
266
			'url'    => "http://www.mangahere.co/manga/{$title}/{$chapter}/",
267
			'number' => $chapter
268
		];
269
	}
270
271 2
	public function getTitleData(string $title_url) {
272 2
		$titleData = [];
273
274 2
		$fullURL = $this->getFullTitleURL($title_url);
275 2
		$content = $this->get_content($fullURL);
276
277 2
		$data = $this->parseTitleDataDOM(
278
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 275 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...
279
			$title_url,
280 2
			"//meta[@property='og:title']/@content",
281 2
			"//body/section/article/div/div[@class='manga_detail']/div[@class='detail_list']/ul[1]/li[1]",
282 2
			"span[@class='right']",
283 2
			"span[@class='left']/a",
284 2
			"<div class=\"error_text\">Sorry, the page you have requested can’t be found."
285
		);
286 2
		if($data) {
287 1
			$titleData['title'] = $data['nodes_title']->textContent;
288
289 1
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
290 1
			$chapterURLSegments = explode('/', $link);
291 1
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
292 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
293
		}
294
295 2
		return (!empty($titleData) ? $titleData : NULL);
296
	}
297
}
298
299
class Batoto extends Site_Model {
300
	//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.
301
	//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.
302
	//title_url is stored like: "TITLE_URL:--:LANGUAGE"
303
	//chapter_urls are stored like "CHAPTER_ID:--:CHAPTER_NUMBER"
304
305
	public $site          = 'Batoto';
306
	public $titleFormat   = '/^[0-9]+:--:(?:English|Spanish|French|German|Portuguese|Turkish|Indonesian|Greek|Filipino|Italian|Polish|Thai|Malay|Hungarian|Romanian|Arabic|Hebrew|Russian|Vietnamese|Dutch)$/';
307
	//FIXME: We're not validating the chapter name since we don't know what all the possible valid characters can be
308
	//       Preferably we'd just use /^[0-9a-z]+:--:(v[0-9]+\/)?c[0-9]+(\.[0-9]+)?$/
309
	public $chapterFormat = '/^[0-9a-z]+:--:.+$/';
310
311 1
	public function getFullTitleURL(string $title_string) : string {
312
		//FIXME: This does not point to the language specific title page. Should ask if it is possible to set LANG as arg?
313
		//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.
314 1
		$title_parts = explode(':--:', $title_string);
315 1
		return "http://bato.to/comic/_/comics/-r".$title_parts[0];
316
	}
317
318 View Code Duplication
	public function getChapterData(string $title_string, string $chapter) : array {
319
		//$title_string isn't used here.
320
321
		$chapter_parts = explode(':--:', $chapter);
322
		return [
323
			'url'    => "http://bato.to/reader#" . $chapter_parts[0],
324
			'number' => $chapter_parts[1]
325
		];
326
	}
327
328 1
	public function getTitleData(string $title_url) {
329 1
		$titleData = [];
330
331 1
		$title_parts = explode(':--:', $title_url);
332 1
		$fullURL     = $this->getFullTitleURL($title_url);
333 1
		$lang        = $title_parts[1]; //TODO: Validate title_lang from array?
334
335
336
		//Bato.to is annoying and locks stuff behind auth. See: https://github.com/DakuTree/manga-tracker/issues/14#issuecomment-233830855
337
		$cookies = [
338 1
			"lang_option={$lang}",
339 1
			"member_id={$this->config->item('batoto_cookie_member_id')}",
340 1
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
341
		];
342 1
		$content = $this->get_content($fullURL, implode("; ", $cookies), "", TRUE);
343
344 1
		$data = $this->parseTitleDataDOM(
345
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($full...', $cookies), '', TRUE) on line 342 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...
346
			$title_url,
347 1
			"//h1[@class='ipsType_pagetitle']",
348 1
			"//table[contains(@class, 'chapters_list')]/tbody/tr[2]",
349 1
			"td[last()]",
350 1
			"td/a[contains(@href,'reader')]",
351 1
			">Register now<"
352
		);
353 1
		if($data) {
354
			$titleData['title'] = html_entity_decode(trim($data['nodes_title']->textContent));
355
356
			///^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/
357
			preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($data['nodes_chapter']->nodeValue), $text);
358
			$titleData['latest_chapter'] = substr($data['nodes_chapter']->getAttribute('href'), 22) . ':--:' . ((!empty($text['volume']) ? 'v'.$text['volume'].'/' : '') . 'c'.$text['chapter'] . (!empty($text['extra']) ? '-'.$text['extra'] : ''));
359
360
			$dateString = $data['nodes_latest']->nodeValue;
361
			if($dateString == 'An hour ago') {
362
				$dateString = '1 hour ago';
363
			}
364
			$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
365
		}
366
367 1
		return (!empty($titleData) ? $titleData : NULL);
368
	}
369
370
	public function cleanTitleDataDOM(string $data) : string {
371
		$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
372
		$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
373
		$data = preg_replace('/<div id=\'commentsStart\' class=\'ipsBox\'>[\s\S]+$/', '</div></div><!-- end mainContent -->', $data);
374
375
		return $data;
376
	}
377
}
378
379
class DynastyScans extends Site_Model {
380
	//FIXME: This has some major issues. SEE: https://github.com/DakuTree/manga-tracker/issues/58
381
382
	public $site          = 'DynastyScans';
383
	public $titleFormat   = '/^[a-z0-9_]+:--:(?:0|1)$/';
384
	public $chapterFormat = '/^[0-9a-z_]+$/';
385
386 1
	public function getFullTitleURL(string $title_string) : string {
387 1
		$title_parts = explode(':--:', $title_string);
388 1
		$url_type = ($title_parts[1] == '0' ? 'series' : 'chapters');
389
390 1
		return 'http://dynasty-scans.com/'.$url_type.'/'.$title_parts[0];
391
	}
392
393
	public function getChapterData(string $title_string, string $chapter) : array {
394
		$title_parts = explode(':--:', $title_string);
395
		/* Known chapter url formats (# is numbers):
396
		       chapters_#A_#B - Ch#A-#B
397
		       ch_#A          - Ch#A
398
		       ch_#A_#B       - Ch#A.#B
399
		       <NOTHING>      - Oneshot (This is passed as "oneshot")
400
		*/
401
402
		$chapterData = [
403
			'url'    => 'http://dynasty-scans.com/chapters/' . $title_parts[0].'_'.$chapter,
404
			'number' => ''
405
		];
406
407
		if($chapter == 'oneshot') {
408
			$chapterData['number'] = 'oneshot';
409
		} else {
410
			$chapter = preg_replace("/^([a-zA-Z]+)/", '$1_', $chapter);
411
			$chapterSegments = explode('_', $chapter);
412
			switch($chapterSegments[0]) {
413
				case 'ch':
414
					$chapterData['number'] = 'c'.$chapterSegments[1].(isset($chapterSegments[2]) && !empty($chapterSegments[2]) ? '.'.$chapterSegments[2] : '');
415
					break;
416
417
				case 'chapters':
418
					//This is barely ever used, but I have seen it.
419
					$chapterData['number'] = 'c'.$chapterSegments[1].'-'.$chapterSegments[2];
420
					break;
421
422
				default:
423
					//TODO: FALLBACK, ALERT ADMIN?
424
					$chapterData['number'] = $chapter;
425
					break;
426
			}
427
		}
428
		return $chapterData;
429
	}
430
431 1
	public function getTitleData(string $title_url) {
432 1
		$titleData = [];
433
434 1
		$fullURL = $this->getFullTitleURL($title_url);
435 1
		$content = $this->get_content($fullURL);
436
437 1
		$title_parts = explode(':--:', $title_url);
438 1
		switch($title_parts[1]) {
439 1
			case '0':
440
				//Normal series.
441 1
				$data = $this->parseTitleDataDOM(
442
					$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 435 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...
443
					$title_url,
444 1
					"//h2[@class='tag-title']/b[1]",
445 1
					"(//dl[@class='chapter-list']/dd[a[contains(@href,'/chapters/')]])[last()]",
446 1
					"small",
447 1
					"a[@class='name']"
448
				);
449 1
				if($data) {
450 1
					$titleData['title'] = $data['nodes_title']->textContent;
451
					//In cases where the series is a doujin, try and prepend the copyright.
452 1
					preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $content['body'], $matchesD);
453 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...
454
						$titleData['title'] = substr($matchesD[1], 0, -7).' - '.$titleData['title'];
455
					}
456
457 1
					$chapterURLSegments = explode('/', (string) $data['nodes_chapter']->getAttribute('href'));
458 1
					$titleData['latest_chapter'] = substr($chapterURLSegments[2], strlen($title_parts[0])+1);
459
460 1
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime(str_replace("'", '', substr((string) $data['nodes_latest']->textContent, 9))));
461
				}
462 1
				break;
463
464
			case '1':
465
				//Oneshot.
466
				$data = $content['body'];
467
468
				preg_match('/<b>.*<\/b>/', $data, $matchesT);
469
				preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $data, $matchesD);
470
				$titleData['title'] = (!empty($matchesD) ? ($matchesD[1] !== 'Original' ? $matchesD[1].' - ' : '') : '') . substr($matchesT[0], 3, -4);
471
472
				$titleData['latest_chapter'] = 'oneshot'; //This will never change
473
474
				preg_match('/<i class="icon-calendar"><\/i> (.*)<\/span>/', $data, $matches);
475
				$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime($matches[1]));
476
477
				//Oneshots are special, and really shouldn't need to be re-tracked
478
				$titleData['status'] = '2';
479
				break;
480
481
			default:
482
				//something went wrong
483
				break;
484
		}
485 1
		return (!empty($titleData) ? $titleData : NULL);
486
	}
487
}
488
489
class MangaPanda extends Site_Model {
490
	public $site          = 'MangaPanda';
491
	//NOTE: MangaPanda has manga pages under the root URL, so we need to filter out pages we know that aren't manga.
492
	public $titleFormat   = '/^(?!(?:latest|search|popular|random|alphabetical|privacy)$)([a-z0-9-]+)$/';
493
	public $chapterFormat = '/^[0-9]+$/';
494
495 2
	public function getFullTitleURL(string $title_url) : string {
496 2
		return "http://www.mangapanda.com/{$title_url}";
497
	}
498
499
	public function getChapterData(string $title_url, string $chapter) : array {
500
		return [
501
			'url'    => "http://www.mangapanda.com/{$title_url}/{$chapter}/",
502
			'number' => 'c'.$chapter
503
		];
504
	}
505
506 2
	public function getTitleData(string $title_url) {
507 2
		$titleData = [];
508
509 2
		$fullURL = $this->getFullTitleURL($title_url);
510 2
		$content = $this->get_content($fullURL);
511
512 2
		$data = $this->parseTitleDataDOM(
513
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 510 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...
514
			$title_url,
515 2
			"//h2[@class='aname']",
516 2
			"(//table[@id='listing']/tr)[last()]",
517 2
			"td[2]",
518 2
			"td[1]/a"
519
		);
520 2
		if($data) {
521 1
			$titleData['title'] = $data['nodes_title']->textContent;
522
523 1
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
524
525 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
526
		}
527
528 2
		return (!empty($titleData) ? $titleData : NULL);
529
	}
530
}
531
532
class MangaStream extends Site_Model {
533
	public $site          = 'MangaStream';
534
	public $titleFormat   = '/^[a-z0-9_]+$/';
535
	public $chapterFormat = '/^(.*?)\/[0-9]+$/';
536
537
	public function getFullTitleURL(string $title_url) : string {
538
		return "https://mangastream.com/manga/{$title_url}/";
539
	}
540
541
	public function getChapterData(string $title_url, string $chapter) : array {
542
		return [
543
			'url'    => "https://mangastream.com/r/{$title_url}/{$chapter}",
544
			'number' => 'c'.explode('/', $chapter)[0]
545
		];
546
	}
547
548
	public function getTitleData(string $title_url) {
549
		$titleData = [];
550
551
		$fullURL = $this->getFullTitleURL($title_url);
552
		$content = $this->get_content($fullURL);
553
554
		$data = $this->parseTitleDataDOM(
555
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 552 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...
556
			$title_url,
557
			"//div[contains(@class, 'content')]/div[1]/h1",
558
			"//div[contains(@class, 'content')]/div[1]/table/tr[2]",
559
			"td[2]",
560
			"td[1]/a",
561
			"<h1>Page Not Found</h1>"
562
		);
563
		if($data) {
564
			$titleData['title'] = $data['nodes_title']->textContent;
565
566
			$titleData['latest_chapter'] = preg_replace('/^.*\/(.*?\/[0-9]+)\/[0-9]+$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
567
568
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
569
		}
570
571
		return (!empty($titleData) ? $titleData : NULL);
572
	}
573
}
574
575
class WebToons extends Site_Model {
576
	/* Webtoons.com has a very weird and pointless URL format.
577
	   TITLE URL:   /#LANG#/#GENRE#/#TITLE#/list?title_no=#TITLEID#
578
	   RSS URL:     /#LANG#/#GENRE#/#TITLE#/rss?title_no=#TITLEID#
579
	   CHAPTER URL: /#LANG#/#GENRE#/#TITLE#/#CHAPTER#/viewer?title_no=#TITLEID#&episode_no=#CHAPTERID#
580
581
	   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).
582
	   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.
583
	   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.
584
585
	   FORMATS:
586
	   TITLE_URL: ID:--:LANG:--:TITLE:--:GENRE
587
	   CHAPTER:   ID:--:CHAPTER_N
588
	*/
589
	//private $validLang = ['en', 'zh-hant', 'zh-hans', 'th', 'id'];
590
591
	public $site          = 'WebToons';
592
	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)$/';
593
	public $chapterFormat = '/^[0-9]+:--:.*$/';
594
595
	public function getFullTitleURL(string $title_url) : string {
596
		$title_parts = explode(':--:', $title_url);
597
		return "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/list?title_no={$title_parts[0]}/";
598
	}
599
600
	public function getChapterData(string $title_url, string $chapter) : array {
601
		$title_parts   = explode(':--:', $title_url);
602
		$chapter_parts = explode(':--:', $chapter);
603
604
		return [
605
			'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]}",
606
			'number' => $chapter_parts[1] //TODO: Possibly replace certain formats in here? Since webtoons doesn't have a standard chapter format
607
		];
608
	}
609
610 1
	public function getTitleData(string $title_url) {
611 1
		$titleData = [];
612
613
		//FIXME: We don't use parseTitleDOM here due to using rss. Should probably have an alternate method for XML parsing.
614
615
		//NOTE: getTitleData uses a different FullTitleURL due to it grabbing the rss ver. instead.
616 1
		$title_parts = explode(':--:', $title_url);
617 1
		$fullURL = "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/rss?title_no={$title_parts[0]}";
618
619 1
		$content = $this->get_content($fullURL);
620 1
		$data = $content['body'];
621 1
		if($data !== 'Can\'t find the manga series.') { //FIXME: We should check for he proper error here.
622 1
			$xml = simplexml_load_string($data) or die("Error: Cannot create object");
623 1
			if(isset($xml->{'channel'}->item[0])) {
624 1
				$titleData['title'] = trim((string) $xml->{'channel'}->title);
625
626 1
				$chapterURLSegments = explode('/', ((string) $xml->{'channel'}->item[0]->link));
627 1
				$titleData['latest_chapter'] = preg_replace('/^.*?([0-9]+)$/', '$1', $chapterURLSegments[7]) . ':--:' . $chapterURLSegments[6];
628 1
				$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $xml->{'channel'}->item[0]->pubDate));
629
			}
630
		} else {
631
			log_message('error', "Series missing? (WebToons): {$title_url}");
632
			return NULL;
633
		}
634
635 1
		return (!empty($titleData) ? $titleData : NULL);
636
	}
637
}
638
639
class KissManga extends Site_Model {
640
	/* 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.
641
	   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.
642
643
	   To generate this cookie, we need three variables. Two are static, but the other is generated by randomly generated JS on the page.
644
	   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.
645
	   The other option is using regex, but that is a rabbit hole I don't want to touch with a ten-foot pole.
646
647
	   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.
648
	   The cookie has a length of 1 year, but I don't think it actually lasts that long, so we update every 6hours instead.
649
	   I should probably also mention that the cookie generated also uses your user-agent, so if it changes the cookie will break.
650
	*/
651
652
	public $site          = 'KissManga';
653
	public $titleFormat   = '/^[A-Za-z0-9-]+$/';
654
	public $chapterFormat = '/^.*?:--:[0-9]+$/';
655
656
	public function getFullTitleURL(string $title_url) : string {
657
		return "http://kissmanga.com/Manga/{$title_url}";
658
	}
659
660 View Code Duplication
	public function getChapterData(string $title_url, string $chapter) : array {
661
		$chapter_parts = explode(':--:', $chapter);
662
663
		return [
664
			'url'    => "http://kissmanga.com/Manga/{$title_url}/{$chapter_parts[0]}?id={$chapter_parts[1]}",
665
			//FIXME: KM has an extremely inconsistant chapter format which makes it difficult to parse.
666
			'number' => /*preg_replace('/--.*?$/', '', */$chapter_parts[0]/*)*/
667
		];
668
	}
669
670
	public function getTitleData(string $title_url) {
671
		$titleData = [];
672
673
		//Check if cookiejar is a day old (so we can know if something went wrong)
674
		$cookiejar_path = str_replace("public/", "_scripts/cookiejar", FCPATH);
675
		$cookie_last_updated = filemtime($cookiejar_path);
676
		if($cookie_last_updated && ((time() - 86400) < $cookie_last_updated)) {
677
678
			$fullURL = $this->getFullTitleURL($title_url);
679
680
			$content = $this->get_content($fullURL, '', $cookiejar_path);
681
			$data = $content['body'];
682
			if(strpos($data, 'containerRoot') !== FALSE) {
683
				//FIXME: For whatever reason, we can't grab the entire div without simplexml shouting at us
684
				$data = preg_replace('/^[\S\s]*(<div id="leftside">[\S\s]*)<div id="rightside">[\S\s]*$/', '$1', $data);
685
686
				$dom = new DOMDocument();
687
				libxml_use_internal_errors(true);
688
				$dom->loadHTML($data);
689
				libxml_use_internal_errors(false);
690
691
				$xpath = new DOMXPath($dom);
692
693
				$nodes_title = $xpath->query("//a[@class='bigChar']");
694
				$nodes_row   = $xpath->query("//table[@class='listing']/tr[3]");
695
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
696
					$titleData['title'] = $nodes_title->item(0)->textContent;
697
698
					$firstRow      = $nodes_row->item(0);
699
					$nodes_latest  = $xpath->query("td[2]",   $firstRow);
700
					$nodes_chapter = $xpath->query("td[1]/a", $firstRow);
701
702
					$link = (string) $nodes_chapter->item(0)->getAttribute('href');
703
					$chapterURLSegments = explode('/', preg_replace('/\?.*$/', '', $link));
704
					$titleData['latest_chapter'] = $chapterURLSegments[3] . ':--:' . preg_replace('/.*?([0-9]+)$/', '$1', $link);
705
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $nodes_latest->item(0)->textContent));
706
				}
707
			} 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...
708
				//TODO: Throw ERRORS;
709
			}
710
		} 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...
711
			//Do nothing, wait until next update.
712
			//TODO: NAG ADMIN??
713
		}
714
715
		return (!empty($titleData) ? $titleData : NULL);
716
	}
717
}
718
719
class GameOfScanlation extends Site_Model {
720
	public $site          = 'GameOfScanlation';
721
	public $titleFormat   = '/^[a-z0-9\.-]+$/';
722
	public $chapterFormat = '/^[a-z0-9\.-]+$/';
723
724
	public function getFullTitleURL(string $title_url) : string {
725
		/* NOTE: GoS is a bit weird in that it has two separate title URL formats. One uses /projects/ and the other uses /fourms/.
726
		         The bad thing is these are interchangeable, despite them showing the exact same listing page.
727
		         Thankfully the title_url of manga which use /forums/ seem to be appended with ".%ID%" which means we can easily check them. */
728
729
		if (strpos($title_url, '.') !== FALSE) {
730
			$format = "https://gameofscanlation.moe/forums/{$title_url}/";
731
		} else {
732
			$format = "https://gameofscanlation.moe/projects/{$title_url}/";
733
		}
734
		return $format;
735
	}
736
737
	public function getChapterData(string $title_url, string $chapter) : array {
738
		return [
739
			'url'    => "https://gameofscanlation.moe/projects/".preg_replace("/\\.[0-9]+$/", "", $title_url).'/'.$chapter.'/',
740
			'number' => preg_replace("/chapter-/", "c", preg_replace("/\\.[0-9]+$/", "", $chapter))
741
		];
742
	}
743
744 View Code Duplication
	public function getTitleData(string $title_url) {
745
		$titleData = [];
746
747
		$fullURL = $this->getFullTitleURL($title_url);
748
749
		$content = $this->get_content($fullURL);
750
751
		$data = $this->parseTitleDataDOM(
752
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 749 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...
753
			$title_url,
754
			"//meta[@property='og:title']",
755
			"//ol[@class='discussionListItems']/li[1]/div[@class='home_list']/ul/li/div[@class='list_press_text']",
756
			"p[@class='author']/span|p[@class='author']/abbr",
757
			"p[@class='text_work']/a"
758
		);
759
		if($data) {
760
			$titleData['title'] = trim(html_entity_decode($data['nodes_title']->getAttribute('content')));
761
762
			$titleData['latest_chapter'] = preg_replace('/^projects\/.*?\/(.*?)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
763
764
			$titleData['last_updated'] =  date("Y-m-d H:i:s",(int) $data['nodes_latest']->getAttribute('title'));
765
		}
766
767
		return (!empty($titleData) ? $titleData : NULL);
768
	}
769
}
770
771
class MangaCow extends Site_Model {
772
	public $site          = 'MangaCow';
773
	public $titleFormat   = '/^[a-zA-Z0-9_]+$/';
774
	public $chapterFormat = '/^[0-9]+$/';
775
776 2
	public function getFullTitleURL(string $title_url) : string {
777 2
		return "http://mngcow.co/{$title_url}/";
778
	}
779
780
	public function getChapterData(string $title_url, string $chapter) : array {
781
		return [
782
			'url'    => $this->getFullTitleURL($title_url).$chapter.'/',
783
			'number' => "c{$chapter}"
784
		];
785
	}
786
787 2 View Code Duplication
	public function getTitleData(string $title_url) {
788 2
		$titleData = [];
789
790 2
		$fullURL = $this->getFullTitleURL($title_url);
791
792 2
		$content = $this->get_content($fullURL);
793
794 2
		$data = $this->parseTitleDataDOM(
795
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 792 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...
796
			$title_url,
797 2
			"//h4",
798 2
			"//ul[contains(@class, 'mng_chp')]/li[1]/a[1]",
799 2
			"b[@class='dte']",
800 2
			"",
801 2
			"404 Page Not Found"
802
		);
803 2
		if($data) {
804 1
			$titleData['title'] = trim($data['nodes_title']->textContent);
805
806 1
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
807
808 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) substr($data['nodes_latest']->getAttribute('title'), 13)));
809
		}
810
811 2
		return (!empty($titleData) ? $titleData : NULL);
812
	}
813
}
814
815
/*** FoolSlide sites ***/
816
817 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...
818
	public $site          = 'KireiCake';
819
	public $titleFormat   = '/^[a-z0-9_-]+$/';
820
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
821
822 2
	public function getFullTitleURL(string $title_url) : string {
823 2
		return "https://reader.kireicake.com/series/{$title_url}";
824
	}
825
826
	public function getChapterData(string $title_url, string $chapter) : array {
827
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
828
		$chapter_parts = explode('/', $chapter);
829
		return [
830
			'url'    => "https://reader.kireicake.com/read/{$title_url}/{$chapter}/",
831
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
832
		];
833
	}
834
835 2
	public function getTitleData(string $title_url) {
836 2
		$fullURL = $this->getFullTitleURL($title_url);
837 2
		return $this->parseFoolSlide($fullURL, $title_url);
838
	}
839
}
840
841 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...
842
	public $site          = 'SeaOtterScans';
843
	public $titleFormat   = '/^[a-z0-9_-]+$/';
844
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
845
846 2
	public function getFullTitleURL(string $title_url) : string {
847 2
		return "https://reader.seaotterscans.com/series/{$title_url}";
848
	}
849
850
	public function getChapterData(string $title_url, string $chapter) : array {
851
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
852
		$chapter_parts = explode('/', $chapter);
853
		return [
854
			'url'    => "https://reader.seaotterscans.com/read/{$title_url}/{$chapter}/",
855
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
856
		];
857
	}
858
859 2
	public function getTitleData(string $title_url) {
860 2
		$fullURL = $this->getFullTitleURL($title_url);
861 2
		return $this->parseFoolSlide($fullURL, $title_url);
862
	}
863
}
864
865 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...
866
	public $site          = 'HelveticaScans';
867
	public $titleFormat   = '/^[a-z0-9_-]+$/';
868
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
869
870 2
	public function getFullTitleURL(string $title_url) : string {
871 2
		return "http://helveticascans.com/reader/series/{$title_url}";
872
	}
873
874
	public function getChapterData(string $title_url, string $chapter) : array {
875
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
876
		$chapter_parts = explode('/', $chapter);
877
		return [
878
			'url'    => "http://helveticascans.com/reader/read/{$title_url}/{$chapter}/",
879
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
880
		];
881
	}
882
883 2
	public function getTitleData(string $title_url) {
884 2
		$fullURL = $this->getFullTitleURL($title_url);
885 2
		return $this->parseFoolSlide($fullURL, $title_url);
886
	}
887
}
888
889 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...
890
	public $site          = 'SenseScans';
891
	public $titleFormat   = '/^[a-z0-9_-]+$/';
892
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
893
894 2
	public function getFullTitleURL(string $title_url) : string {
895 2
		return "http://reader.sensescans.com/series/{$title_url}";
896
	}
897
898
	public function getChapterData(string $title_url, string $chapter) : array {
899
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
900
		$chapter_parts = explode('/', $chapter);
901
		return [
902
			'url'    => "http://reader.sensescans.com/read/{$title_url}/{$chapter}/",
903
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
904
		];
905
	}
906
907 2
	public function getTitleData(string $title_url) {
908 2
		$fullURL = $this->getFullTitleURL($title_url);
909 2
		return $this->parseFoolSlide($fullURL, $title_url);
910
	}
911
}
912
913 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...
914
	public $site          = 'JaiminisBox';
915
	public $titleFormat   = '/^[a-z0-9_-]+$/';
916
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
917
918
	public function getFullTitleURL(string $title_url) : string {
919
		return "https://jaiminisbox.com/reader/series/{$title_url}";
920
	}
921
922
	public function getChapterData(string $title_url, string $chapter) : array {
923
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
924
		$chapter_parts = explode('/', $chapter);
925
		return [
926
			'url'    => "https://jaiminisbox.com/reader/read/{$title_url}/{$chapter}/",
927
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
928
		];
929
	}
930
931
	public function getTitleData(string $title_url) {
932
		$fullURL = $this->getFullTitleURL($title_url);
933
		return $this->parseFoolSlide($fullURL, $title_url);
934
	}
935
}
936