Completed
Push — master ( 83a0f0...004135 )
by Angus
03:33
created

WebToons::getChapterData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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