Completed
Push — master ( 6f854c...d6ef10 )
by Angus
03:31
created

DynastyScans::getTitleData()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 56
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 35
nc 16
nop 1
dl 0
loc 56
ccs 20
cts 20
cp 1
crap 9
rs 7.1584
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 $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 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...
89 18
		$status_code = $content['status_code'];
90 18
		$data        = $content['body'];
91
92 18
		if(!is_array($content)) {
93
			log_message('error', "{$this->site} : {$title_url} | Failed to grab URL (See above curl error)");
94 18
		} else if(!($status_code >= 200 && $status_code < 300)) {
95 8
			log_message('error', "{$this->site} : {$title_url} | Bad Status Code ({$status_code})");
96 10
		} else if(empty($data)) {
97
			log_message('error', "{$this->site} : {$title_url} | Data is empty? (Status code: {$status_code})");
98 10
		} else if($failure_string !== "" && strpos($data, $failure_string) !== FALSE) {
99 1
			log_message('error', "{$this->site} : {$title_url} | Failure string matched");
100
		} else {
101 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.
102
103 9
			$dom = new DOMDocument();
104 9
			libxml_use_internal_errors(TRUE);
105 9
			$dom->loadHTML($data);
106 9
			libxml_use_internal_errors(FALSE);
107
108 9
			$xpath = new DOMXPath($dom);
109 9
			$nodes_title = $xpath->query($node_title_string);
110 9
			$nodes_row   = $xpath->query($node_row_string);
111 9
			if($nodes_title->length === 1 && $nodes_row->length === 1) {
112 9
				$firstRow      = $nodes_row->item(0);
113 9
				$nodes_latest  = $xpath->query($node_latest_string,  $firstRow);
114
115 9
				if($node_chapter_string !== '') {
116 8
					$nodes_chapter = $xpath->query($node_chapter_string, $firstRow);
117
				} else {
118 1
					$nodes_chapter = $nodes_row;
119
				}
120
121 9
				if($nodes_latest->length === 1 && $nodes_chapter->length === 1) {
122
					return [
123 9
						'nodes_title'   => $nodes_title->item(0),
124 9
						'nodes_latest'  => $nodes_latest->item(0),
125 9
						'nodes_chapter' => $nodes_chapter->item(0)
126
					];
127
				} else {
128
					log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (LATEST: {$nodes_latest->length} | CHAPTER: {$nodes_chapter->length})");
129
				}
130
			} else {
131
				log_message('error', "{$this->site} : {$title_url} | Invalid amount of nodes (TITLE: {$nodes_title->length} | ROW: {$nodes_row->length})");
132
			}
133
		}
134 9
		return FALSE;
135
	}
136
137 9
	public function cleanTitleDataDOM(string $data) : string {
138 9
		return $data;
139
	}
140
141
	//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.
142 8
	final public function parseFoolSlide(string $fullURL, string $title_url) {
143 8
		$titleData = [];
144
145 8
		$content         = $this->get_content($fullURL);
146 8
		$content['body'] = preg_replace('/^[\S\s]*(<article[\S\s]*)<\/article>[\S\s]*$/', '$1', $content['body']);
147
148 8
		$data = $this->parseTitleDataDOM(
149
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 145 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...
150
			$title_url,
151 8
			"//div[@class='large comic']/h1[@class='title']",
152 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]",
153 8
			"div[@class='meta_r']",
154 8
			"div[@class='title']/a"
155
		);
156 8
		if($data) {
157 4
			$titleData['title'] = trim($data['nodes_title']->textContent);
158
159 4
			$link = (string) $data['nodes_chapter']->getAttribute('href');
160 4
			$titleData['latest_chapter'] = preg_replace('/.*\/read\/.*?\/(.*?)\/$/', '$1', $link);
161
162 4
			$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime((string) str_replace('.', '', explode(',', $data['nodes_latest']->nodeValue)[1])));
163
		}
164
165 8
		return (!empty($titleData) ? $titleData : NULL);
166
	}
167
}
168
class Sites_Model extends CI_Model {
169
	public $MangaFox;
170
	public $MangaHere;
171
	public $Batoto;
172
	public $DynastyScans;
173
	public $MangaPanda;
174
	public $MangaStream;
175
	public $WebToons;
176
	public $KissManga;
177
	public $KireiCake;
178
	public $GameOfScanlation;
179
	public $MangaCow;
180
	public $SeaOtterScans;
181
	public $HelveticaScans;
182
	public $SenseScans;
183
184 121
	public function __construct() {
185 121
		parent::__construct();
186
187 121
		$this->MangaFox         = new MangaFox();
188 121
		$this->MangaHere        = new MangaHere();
189 121
		$this->Batoto           = new Batoto();
190 121
		$this->DynastyScans     = new DynastyScans();
191 121
		$this->MangaPanda       = new MangaPanda();
192 121
		$this->MangaStream      = new MangaStream();
193 121
		$this->WebToons         = new WebToons();
194 121
		$this->KissManga        = new KissManga();
195 121
		$this->KireiCake        = new KireiCake();
196 121
		$this->GameOfScanlation = new GameOfScanlation();
197 121
		$this->MangaCow         = new MangaCow();
198 121
		$this->SeaOtterScans    = new SeaOtterScans();
199 121
		$this->HelveticaScans   = new HelveticaScans();
200 121
		$this->SenseScans       = new SenseScans();
201 121
	}
202
}
203
204
class MangaFox extends Site_Model {
205
	public $site          = 'MangaFox';
206
	public $titleFormat   = '/^[a-z0-9_]+$/';
207
	public $chapterFormat = '/^(?:v[0-9a-zA-Z]+\/)?c[0-9\.]+$/';
208
209 2
	public function getFullTitleURL(string $title_url) : string {
210 2
		return "http://mangafox.me/manga/{$title_url}/";
211
	}
212
213
	public function getChapterData(string $title_url, string $chapter) : array {
214
		return [
215
			'url'    => "http://mangafox.me/manga/{$title_url}/{$chapter}/",
216
			'number' => $chapter
217
		];
218
	}
219
220 2
	public function getTitleData(string $title_url) {
221 2
		$titleData = [];
222
223 2
		$fullURL = $this->getFullTitleURL($title_url);
224 2
		$content = $this->get_content($fullURL);
225
226 2
		$data = $this->parseTitleDataDOM(
227
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 224 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...
228
			$title_url,
229 2
			"//meta[@property='og:title']/@content",
230 2
			"//body/div[@id='page']/div[@class='left']/div[@id='chapters']/ul[1]/li[1]",
231 2
			"div/span[@class='date']",
232 2
			"div/h3/a"
233
		);
234 2
		if($data) {
235 1
			$titleData['title'] = html_entity_decode(substr($data['nodes_title']->textContent, 0, -6));
236
237 1
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
238 1
			$chapterURLSegments = explode('/', $link);
239 1
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
240 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
241
		}
242
243 2
		return (!empty($titleData) ? $titleData : NULL);
244
	}
245
}
246
247
class MangaHere extends Site_Model {
248
	public $site          = 'MangaHere';
249
	public $titleFormat   = '/^[a-z0-9_]+$/';
250
	public $chapterFormat = '/^(?:v[0-9]+\/)?c[0-9]+(?:\.[0-9]+)?$/';
251
252 2
	public function getFullTitleURL(string $title_url) : string {
253 2
		return "http://www.mangahere.co/manga/{$title_url}/";
254
	}
255
256
	public function getChapterData(string $title, string $chapter) : array {
257
		return [
258
			'url'    => "http://www.mangahere.co/manga/{$title}/{$chapter}/",
259
			'number' => $chapter
260
		];
261
	}
262
263 2
	public function getTitleData(string $title_url) {
264 2
		$titleData = [];
265
266 2
		$fullURL = $this->getFullTitleURL($title_url);
267 2
		$content = $this->get_content($fullURL);
268
269 2
		$data = $this->parseTitleDataDOM(
270
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 267 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...
271
			$title_url,
272 2
			"//meta[@property='og:title']/@content",
273 2
			"//body/section/article/div/div[@class='manga_detail']/div[@class='detail_list']/ul[1]/li[1]",
274 2
			"span[@class='right']",
275 2
			"span[@class='left']/a",
276 2
			"<div class=\"error_text\">Sorry, the page you have requested can’t be found."
277
		);
278 2
		if($data) {
279 1
			$titleData['title'] = $data['nodes_title']->textContent;
280
281 1
			$link = preg_replace('/^(.*\/)(?:[0-9]+\.html)?$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
282 1
			$chapterURLSegments = explode('/', $link);
283 1
			$titleData['latest_chapter'] = $chapterURLSegments[5] . (isset($chapterURLSegments[6]) && !empty($chapterURLSegments[6]) ? "/{$chapterURLSegments[6]}" : "");
284 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
285
		}
286
287 2
		return (!empty($titleData) ? $titleData : NULL);
288
	}
289
}
290
291
class Batoto extends Site_Model {
292
	//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.
293
	//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.
294
	//title_url is stored like: "TITLE_URL:--:LANGUAGE"
295
	//chapter_urls are stored like "CHAPTER_ID:--:CHAPTER_NUMBER"
296
297
	public $site          = 'Batoto';
298
	public $titleFormat   = '/^[0-9]+:--:(?:English|Spanish|French|German|Portuguese|Turkish|Indonesian|Greek|Filipino|Italian|Polish|Thai|Malay|Hungarian|Romanian|Arabic|Hebrew|Russian|Vietnamese|Dutch)$/';
299
	//FIXME: We're not validating the chapter name since we don't know what all the possible valid characters can be
300
	//       Preferably we'd just use /^[0-9a-z]+:--:(v[0-9]+\/)?c[0-9]+(\.[0-9]+)?$/
301
	public $chapterFormat = '/^[0-9a-z]+:--:.+$/';
302
303 1
	public function getFullTitleURL(string $title_string) : string {
304
		//FIXME: This does not point to the language specific title page. Should ask if it is possible to set LANG as arg?
305
		//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.
306 1
		$title_parts = explode(':--:', $title_string);
307 1
		return "http://bato.to/comic/_/comics/-r".$title_parts[0];
308
	}
309
310 View Code Duplication
	public function getChapterData(string $title_string, string $chapter) : array {
311
		//$title_string isn't used here.
312
313
		$chapter_parts = explode(':--:', $chapter);
314
		return [
315
			'url'    => "http://bato.to/reader#" . $chapter_parts[0],
316
			'number' => $chapter_parts[1]
317
		];
318
	}
319
320 1
	public function getTitleData(string $title_url) {
321 1
		$titleData = [];
322
323 1
		$title_parts = explode(':--:', $title_url);
324 1
		$fullURL     = $this->getFullTitleURL($title_url);
325 1
		$lang        = $title_parts[1]; //TODO: Validate title_lang from array?
326
327
328
		//Bato.to is annoying and locks stuff behind auth. See: https://github.com/DakuTree/manga-tracker/issues/14#issuecomment-233830855
329
		$cookies = [
330 1
			"lang_option={$lang}",
331 1
			"member_id={$this->config->item('batoto_cookie_member_id')}",
332 1
			"pass_hash={$this->config->item('batoto_cookie_pass_hash')}"
333
		];
334 1
		$content = $this->get_content($fullURL, implode("; ", $cookies), "", TRUE);
335
336 1
		$data = $this->parseTitleDataDOM(
337
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($full...', $cookies), '', TRUE) on line 334 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...
338
			$title_url,
339 1
			"//h1[@class='ipsType_pagetitle']",
340 1
			"//table[contains(@class, 'chapters_list')]/tbody/tr[2]",
341 1
			"td[last()]",
342 1
			"td/a[contains(@href,'reader')]",
343 1
			">Register now<"
344
		);
345 1
		if($data) {
346
			$titleData['title'] = html_entity_decode(trim($data['nodes_title']->textContent));
347
348
			///^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/
349
			preg_match('/^(?:Vol\.(?<volume>\S+) )?(?:Ch.(?<chapter>[^\s:]+)(?:\s?-\s?(?<extra>[0-9]+))?):?.*/', trim($data['nodes_chapter']->nodeValue), $text);
350
			$titleData['latest_chapter'] = substr($data['nodes_chapter']->getAttribute('href'), 22) . ':--:' . ((!empty($text['volume']) ? 'v'.$text['volume'].'/' : '') . 'c'.$text['chapter'] . (!empty($text['extra']) ? '-'.$text['extra'] : ''));
351
352
			$dateString = $data['nodes_latest']->nodeValue;
353
			if($dateString == 'An hour ago') {
354
				$dateString = '1 hour ago';
355
			}
356
			$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime(preg_replace('/ (-|\[A\]).*$/', '', $dateString)));
357
		}
358
359 1
		return (!empty($titleData) ? $titleData : NULL);
360
	}
361
362
	public function cleanTitleDataDOM(string $data) : string {
363
		$data = preg_replace('/^[\s\S]+<!-- ::: CONTENT ::: -->/', '<!-- ::: CONTENT ::: -->', $data);
364
		$data = preg_replace('/<!-- end mainContent -->[\s\S]+$/', '<!-- end mainContent -->', $data);
365
		$data = preg_replace('/<div id=\'commentsStart\' class=\'ipsBox\'>[\s\S]+$/', '</div></div><!-- end mainContent -->', $data);
366
367
		return $data;
368
	}
369
}
370
371
class DynastyScans extends Site_Model {
372
	//FIXME: This has some major issues. SEE: https://github.com/DakuTree/manga-tracker/issues/58
373
374
	public $site          = 'DynastyScans';
375
	public $titleFormat   = '/^[a-z0-9_]+:--:(?:0|1)$/';
376
	public $chapterFormat = '/^[0-9a-z_]+$/';
377
378 1
	public function getFullTitleURL(string $title_string) : string {
379 1
		$title_parts = explode(':--:', $title_string);
380 1
		$url_type = ($title_parts[1] == '0' ? 'series' : 'chapters');
381
382 1
		return 'http://dynasty-scans.com/'.$url_type.'/'.$title_parts[0];
383
	}
384
385
	public function getChapterData(string $title_string, string $chapter) : array {
386
		$title_parts = explode(':--:', $title_string);
387
		/* Known chapter url formats (# is numbers):
388
		       chapters_#A_#B - Ch#A-#B
389
		       ch_#A          - Ch#A
390
		       ch_#A_#B       - Ch#A.#B
391
		       <NOTHING>      - Oneshot (This is passed as "oneshot")
392
		*/
393
394
		$chapterData = [
395
			'url'    => 'http://dynasty-scans.com/chapters/' . $title_parts[0].'_'.$chapter,
396
			'number' => ''
397
		];
398
399
		if($chapter == 'oneshot') {
400
			$chapterData['number'] = 'oneshot';
401
		} else {
402
			$chapter = preg_replace("/^([a-zA-Z]+)/", '$1_', $chapter);
403
			$chapterSegments = explode('_', $chapter);
404
			switch($chapterSegments[0]) {
405
				case 'ch':
406
					$chapterData['number'] = 'c'.$chapterSegments[1].(isset($chapterSegments[2]) && !empty($chapterSegments[2]) ? '.'.$chapterSegments[2] : '');
407
					break;
408
409
				case 'chapters':
410
					//This is barely ever used, but I have seen it.
411
					$chapterData['number'] = 'c'.$chapterSegments[1].'-'.$chapterSegments[2];
412
					break;
413
414
				default:
415
					//TODO: FALLBACK, ALERT ADMIN?
416
					$chapterData['number'] = $chapter;
417
					break;
418
			}
419
		}
420
		return $chapterData;
421
	}
422
423 1
	public function getTitleData(string $title_url) {
424 1
		$titleData = [];
425
426 1
		$fullURL = $this->getFullTitleURL($title_url);
427 1
		$content = $this->get_content($fullURL);
428
429 1
		switch(explode(':--:', $title_url)[1]) {
430 1
			case '0':
431
				//Normal series.
432 1
				$data = $this->parseTitleDataDOM(
433
					$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 427 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...
434
					$title_url,
435 1
					"//h2[@class='tag-title']/b[1]",
436 1
					"(//dl[@class='chapter-list']/dd[a[contains(@href,'/chapters/')]])[last()]",
437 1
					"small",
438 1
					"a[@class='name']"
439
				);
440 1
				if($data) {
441 1
					$titleData['title'] = $data['nodes_title']->textContent;
442
					//In cases where the series is a doujin, try and prepend the copyright.
443 1
					preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $content['body'], $matchesD);
444 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...
445
						$titleData['title'] = substr($matchesD[1], 0, -7).' - '.$titleData['title'];
446
					}
447
448 1
					$chapterURLSegments = explode('/', (string) $data['nodes_chapter']->getAttribute('href'));
449 1
					$titleData['latest_chapter'] = $chapterURLSegments[2];
450
451 1
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime(str_replace("'", '', substr((string) $data['nodes_latest']->textContent, 9))));
452
				}
453 1
				break;
454
455
			case '1':
456
				//Oneshot.
457
				$data = $content['body'];
458
459
				preg_match('/<b>.*<\/b>/', $data, $matchesT);
460
				preg_match('/\/doujins\/[^"]+">(.+)?(?=<\/a>)<\/a>/', $data, $matchesD);
461
				$titleData['title'] = (!empty($matchesD) ? ($matchesD[1] !== 'Original' ? $matchesD[1].' - ' : '') : '') . substr($matchesT[0], 3, -4);
462
463
				$titleData['latest_chapter'] = 'oneshot'; //This will never change
464
465
				preg_match('/<i class="icon-calendar"><\/i> (.*)<\/span>/', $data, $matches);
466
				$titleData['last_updated']   = date("Y-m-d H:i:s", strtotime($matches[1]));
467
468
				//Oneshots are special, and really shouldn't need to be re-tracked
469
				//FIXME: We need to have a specific "no-track" complete param.
470
				$titleData['complete'] = 'Y';
471
				break;
472
473
			default:
474
				//something went wrong
475
				break;
476
		}
477 1
		return (!empty($titleData) ? $titleData : NULL);
478
	}
479
}
480
481
class MangaPanda extends Site_Model {
482
	public $site          = 'MangaPanda';
483
	//NOTE: MangaPanda has manga pages under the root URL, so we need to filter out pages we know that aren't manga.
484
	public $titleFormat   = '/^(?!(?:latest|search|popular|random|alphabetical|privacy)$)([a-z0-9-]+)$/';
485
	public $chapterFormat = '/^[0-9]+$/';
486
487 2
	public function getFullTitleURL(string $title_url) : string {
488 2
		return "http://www.mangapanda.com/{$title_url}";
489
	}
490
491
	public function getChapterData(string $title_url, string $chapter) : array {
492
		return [
493
			'url'    => "http://www.mangapanda.com/{$title_url}/{$chapter}/",
494
			'number' => 'c'.$chapter
495
		];
496
	}
497
498 2
	public function getTitleData(string $title_url) {
499 2
		$titleData = [];
500
501 2
		$fullURL = $this->getFullTitleURL($title_url);
502 2
		$content = $this->get_content($fullURL);
503
504 2
		$data = $this->parseTitleDataDOM(
505
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 502 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...
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
	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
			$title_url,
549
			"//div[contains(@class, 'content')]/div[1]/h1",
550
			"//div[contains(@class, 'content')]/div[1]/table/tr[2]",
551
			"td[2]",
552
			"td[1]/a",
553
			"<h1>Page Not Found</h1>"
554
		);
555
		if($data) {
556
			$titleData['title'] = $data['nodes_title']->textContent;
557
558
			$titleData['latest_chapter'] = preg_replace('/^.*\/(.*?\/[0-9]+)\/[0-9]+$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
559
560
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $data['nodes_latest']->nodeValue));
561
		}
562
563
		return (!empty($titleData) ? $titleData : NULL);
564
	}
565
}
566
567
class WebToons extends Site_Model {
568
	/* Webtoons.com has a very weird and pointless URL format.
569
	   TITLE URL:   /#LANG#/#GENRE#/#TITLE#/list?title_no=#TITLEID#
570
	   RSS URL:     /#LANG#/#GENRE#/#TITLE#/rss?title_no=#TITLEID#
571
	   CHAPTER URL: /#LANG#/#GENRE#/#TITLE#/#CHAPTER#/viewer?title_no=#TITLEID#&episode_no=#CHAPTERID#
572
573
	   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).
574
	   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.
575
	   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.
576
577
	   FORMATS:
578
	   TITLE_URL: ID:--:LANG:--:TITLE:--:GENRE
579
	   CHAPTER:   ID:--:CHAPTER_N
580
	*/
581
	//private $validLang = ['en', 'zh-hant', 'zh-hans', 'th', 'id'];
582
583
	public $site          = 'WebToons';
584
	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)$/';
585
	public $chapterFormat = '/^[0-9]+:--:.*$/';
586
587
	public function getFullTitleURL(string $title_url) : string {
588
		$title_parts = explode(':--:', $title_url);
589
		return "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/list?title_no={$title_parts[0]}/";
590
	}
591
592
	public function getChapterData(string $title_url, string $chapter) : array {
593
		$title_parts   = explode(':--:', $title_url);
594
		$chapter_parts = explode(':--:', $chapter);
595
596
		return [
597
			'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]}",
598
			'number' => $chapter_parts[1] //TODO: Possibly replace certain formats in here? Since webtoons doesn't have a standard chapter format
599
		];
600
	}
601
602 1
	public function getTitleData(string $title_url) {
603 1
		$titleData = [];
604
605
		//FIXME: We don't use parseTitleDOM here due to using rss. Should probably have an alternate method for XML parsing.
606
607
		//NOTE: getTitleData uses a different FullTitleURL due to it grabbing the rss ver. instead.
608 1
		$title_parts = explode(':--:', $title_url);
609 1
		$fullURL = "http://www.webtoons.com/{$title_parts[1]}/{$title_parts[3]}/{$title_parts[2]}/rss?title_no={$title_parts[0]}";
610
611 1
		$content = $this->get_content($fullURL);
612 1
		$data = $content['body'];
613 1
		if($data !== 'Can\'t find the manga series.') { //FIXME: We should check for he proper error here.
614 1
			$xml = simplexml_load_string($data) or die("Error: Cannot create object");
615 1
			if(isset($xml->{'channel'}->item[0])) {
616 1
				$titleData['title'] = trim((string) $xml->{'channel'}->title);
617
618 1
				$chapterURLSegments = explode('/', ((string) $xml->{'channel'}->item[0]->link));
619 1
				$titleData['latest_chapter'] = preg_replace('/^.*?([0-9]+)$/', '$1', $chapterURLSegments[7]) . ':--:' . $chapterURLSegments[6];
620 1
				$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $xml->{'channel'}->item[0]->pubDate));
621
			}
622
		} else {
623
			log_message('error', "Series missing? (WebToons): {$title_url}");
624
			return NULL;
625
		}
626
627 1
		return (!empty($titleData) ? $titleData : NULL);
628
	}
629
}
630
631
class KissManga extends Site_Model {
632
	/* 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.
633
	   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.
634
635
	   To generate this cookie, we need three variables. Two are static, but the other is generated by randomly generated JS on the page.
636
	   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.
637
	   The other option is using regex, but that is a rabbit hole I don't want to touch with a ten-foot pole.
638
639
	   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.
640
	   The cookie has a length of 1 year, but I don't think it actually lasts that long, so we update every 6hours instead.
641
	   I should probably also mention that the cookie generated also uses your user-agent, so if it changes the cookie will break.
642
	*/
643
644
	public $site          = 'KissManga';
645
	public $titleFormat   = '/^[A-Za-z0-9-]+$/';
646
	public $chapterFormat = '/^.*?:--:[0-9]+$/';
647
648
	public function getFullTitleURL(string $title_url) : string {
649
		return "http://kissmanga.com/Manga/{$title_url}";
650
	}
651
652 View Code Duplication
	public function getChapterData(string $title_url, string $chapter) : array {
653
		$chapter_parts = explode(':--:', $chapter);
654
655
		return [
656
			'url'    => "http://kissmanga.com/Manga/{$title_url}/{$chapter_parts[0]}?id={$chapter_parts[1]}",
657
			//FIXME: KM has an extremely inconsistant chapter format which makes it difficult to parse.
658
			'number' => /*preg_replace('/--.*?$/', '', */$chapter_parts[0]/*)*/
659
		];
660
	}
661
662
	public function getTitleData(string $title_url) {
663
		$titleData = [];
664
665
		//Check if cookiejar is a day old (so we can know if something went wrong)
666
		$cookiejar_path = str_replace("public/", "_scripts/cookiejar", FCPATH);
667
		$cookie_last_updated = filemtime($cookiejar_path);
668
		if($cookie_last_updated && ((time() - 86400) < $cookie_last_updated)) {
669
670
			$fullURL = $this->getFullTitleURL($title_url);
671
672
			$content = $this->get_content($fullURL, '', $cookiejar_path);
673
			$data = $content['body'];
674
			if(strpos($data, 'containerRoot') !== FALSE) {
675
				//FIXME: For whatever reason, we can't grab the entire div without simplexml shouting at us
676
				$data = preg_replace('/^[\S\s]*(<div id="leftside">[\S\s]*)<div id="rightside">[\S\s]*$/', '$1', $data);
677
678
				$dom = new DOMDocument();
679
				libxml_use_internal_errors(true);
680
				$dom->loadHTML($data);
681
				libxml_use_internal_errors(false);
682
683
				$xpath = new DOMXPath($dom);
684
685
				$nodes_title = $xpath->query("//a[@class='bigChar']");
686
				$nodes_row   = $xpath->query("//table[@class='listing']/tr[3]");
687
				if($nodes_title->length === 1 && $nodes_row->length === 1) {
688
					$titleData['title'] = $nodes_title->item(0)->textContent;
689
690
					$firstRow      = $nodes_row->item(0);
691
					$nodes_latest  = $xpath->query("td[2]",   $firstRow);
692
					$nodes_chapter = $xpath->query("td[1]/a", $firstRow);
693
694
					$link = (string) $nodes_chapter->item(0)->getAttribute('href');
695
					$chapterURLSegments = explode('/', preg_replace('/\?.*$/', '', $link));
696
					$titleData['latest_chapter'] = $chapterURLSegments[3] . ':--:' . preg_replace('/.*?([0-9]+)$/', '$1', $link);
697
					$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) $nodes_latest->item(0)->textContent));
698
				}
699
			} 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...
700
				//TODO: Throw ERRORS;
701
			}
702
		} 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...
703
			//Do nothing, wait until next update.
704
			//TODO: NAG ADMIN??
705
		}
706
707
		return (!empty($titleData) ? $titleData : NULL);
708
	}
709
}
710
711
class GameOfScanlation extends Site_Model {
712
	public $site          = 'GameOfScanlation';
713
	public $titleFormat   = '/^[a-z0-9\.-]+$/';
714
	public $chapterFormat = '/^[a-z0-9\.-]+$/';
715
716
	public function getFullTitleURL(string $title_url) : string {
717
		/* NOTE: GoS is a bit weird in that it has two separate title URL formats. One uses /projects/ and the other uses /fourms/.
718
		         The bad thing is these are interchangeable, despite them showing the exact same listing page.
719
		         Thankfully the title_url of manga which use /forums/ seem to be appended with ".%ID%" which means we can easily check them. */
720
721
		if (strpos($title_url, '.') !== FALSE) {
722
			$format = "https://gameofscanlation.moe/forums/{$title_url}/";
723
		} else {
724
			$format = "https://gameofscanlation.moe/projects/{$title_url}/";
725
		}
726
		return $format;
727
	}
728
729
	public function getChapterData(string $title_url, string $chapter) : array {
730
		return [
731
			'url'    => "https://gameofscanlation.moe/projects/".preg_replace("/\\.[0-9]+$/", "", $title_url).'/'.$chapter.'/',
732
			'number' => preg_replace("/chapter-/", "c", preg_replace("/\\.[0-9]+$/", "", $chapter))
733
		];
734
	}
735
736 View Code Duplication
	public function getTitleData(string $title_url) {
737
		$titleData = [];
738
739
		$fullURL = $this->getFullTitleURL($title_url);
740
741
		$content = $this->get_content($fullURL);
742
743
		$data = $this->parseTitleDataDOM(
744
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 741 can also be of type false; however, Site_Model::parseTitleDataDOM() does only seem to accept array, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
745
			$title_url,
746
			"//meta[@property='og:title']",
747
			"//ol[@class='discussionListItems']/li[1]/div[@class='home_list']/ul/li/div[@class='list_press_text']",
748
			"p[@class='author']/span|p[@class='author']/abbr",
749
			"p[@class='text_work']/a"
750
		);
751
		if($data) {
752
			$titleData['title'] = trim(html_entity_decode($data['nodes_title']->getAttribute('content')));
753
754
			$titleData['latest_chapter'] = preg_replace('/^projects\/.*?\/(.*?)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
755
756
			$titleData['last_updated'] =  date("Y-m-d H:i:s",(int) $data['nodes_latest']->getAttribute('title'));
757
		}
758
759
		return (!empty($titleData) ? $titleData : NULL);
760
	}
761
}
762
763
class MangaCow extends Site_Model {
764
	public $site          = 'MangaCow';
765
	public $titleFormat   = '/^[a-zA-Z0-9_]+$/';
766
	public $chapterFormat = '/^[0-9]+$/';
767
768 2
	public function getFullTitleURL(string $title_url) : string {
769 2
		return "http://mngcow.co/{$title_url}/";
770
	}
771
772
	public function getChapterData(string $title_url, string $chapter) : array {
773
		return [
774
			'url'    => $this->getFullTitleURL($title_url).$chapter.'/',
775
			'number' => "c{$chapter}"
776
		];
777
	}
778
779 2 View Code Duplication
	public function getTitleData(string $title_url) {
780 2
		$titleData = [];
781
782 2
		$fullURL = $this->getFullTitleURL($title_url);
783
784 2
		$content = $this->get_content($fullURL);
785
786 2
		$data = $this->parseTitleDataDOM(
787
			$content,
0 ignored issues
show
Security Bug introduced by
It seems like $content defined by $this->get_content($fullURL) on line 784 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...
788
			$title_url,
789 2
			"//h4",
790 2
			"//ul[contains(@class, 'mng_chp')]/li[1]/a[1]",
791 2
			"b[@class='dte']",
792 2
			"",
793 2
			"404 Page Not Found"
794
		);
795 2
		if($data) {
796 1
			$titleData['title'] = trim($data['nodes_title']->textContent);
797
798 1
			$titleData['latest_chapter'] = preg_replace('/^.*\/([0-9]+)\/$/', '$1', (string) $data['nodes_chapter']->getAttribute('href'));
799
800 1
			$titleData['last_updated'] =  date("Y-m-d H:i:s", strtotime((string) substr($data['nodes_latest']->getAttribute('title'), 13)));
801
		}
802
803 2
		return (!empty($titleData) ? $titleData : NULL);
804
	}
805
}
806
807 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...
808
	public $site          = 'KireiCake';
809
	public $titleFormat   = '/^[a-z0-9_-]+$/';
810
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
811
812 2
	public function getFullTitleURL(string $title_url) : string {
813 2
		return "https://reader.kireicake.com/series/{$title_url}";
814
	}
815
816
	public function getChapterData(string $title_url, string $chapter) : array {
817
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
818
		$chapter_parts = explode('/', $chapter);
819
		return [
820
			'url'    => "https://reader.kireicake.com/read/{$title_url}/{$chapter}/",
821
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
822
		];
823
	}
824
825 2
	public function getTitleData(string $title_url) {
826 2
		$fullURL = $this->getFullTitleURL($title_url);
827 2
		return $this->parseFoolSlide($fullURL, $title_url);
828
	}
829
}
830
831 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...
832
	public $site          = 'SeaOtterScans';
833
	public $titleFormat   = '/^[a-z0-9_-]+$/';
834
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
835
836 2
	public function getFullTitleURL(string $title_url) : string {
837 2
		return "https://reader.seaotterscans.com/series/{$title_url}";
838
	}
839
840
	public function getChapterData(string $title_url, string $chapter) : array {
841
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
842
		$chapter_parts = explode('/', $chapter);
843
		return [
844
			'url'    => "https://reader.seaotterscans.com/read/{$title_url}/{$chapter}/",
845
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
846
		];
847
	}
848
849 2
	public function getTitleData(string $title_url) {
850 2
		$fullURL = $this->getFullTitleURL($title_url);
851 2
		return $this->parseFoolSlide($fullURL, $title_url);
852
	}
853
}
854
855 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...
856
	public $site          = 'HelveticaScans';
857
	public $titleFormat   = '/^[a-z0-9_-]+$/';
858
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
859
860 2
	public function getFullTitleURL(string $title_url) : string {
861 2
		return "http://helveticascans.com/reader/series/{$title_url}";
862
	}
863
864
	public function getChapterData(string $title_url, string $chapter) : array {
865
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
866
		$chapter_parts = explode('/', $chapter);
867
		return [
868
			'url'    => "http://helveticascans.com/reader/read/{$title_url}/{$chapter}/",
869
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
870
		];
871
	}
872
873 2
	public function getTitleData(string $title_url) {
874 2
		$fullURL = $this->getFullTitleURL($title_url);
875 2
		return $this->parseFoolSlide($fullURL, $title_url);
876
	}
877
}
878
879 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...
880
	public $site          = 'SenseScans';
881
	public $titleFormat   = '/^[a-z0-9_-]+$/';
882
	public $chapterFormat = '/^en\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+(?:\/[0-9]+)?)?)?$/';
883
884 2
	public function getFullTitleURL(string $title_url) : string {
885 2
		return "http://reader.sensescans.com/series/{$title_url}";
886
	}
887
888
	public function getChapterData(string $title_url, string $chapter) : array {
889
		//LANG/VOLUME/CHAPTER/CHAPTER_EXTRA(/page/)
890
		$chapter_parts = explode('/', $chapter);
891
		return [
892
			'url'    => "http://reader.sensescans.com/read/{$title_url}/{$chapter}/",
893
			'number' => ($chapter_parts[1] !== '0' ? "v{$chapter_parts[1]}/" : '') . "c{$chapter_parts[2]}" . (isset($chapter_parts[3]) ? ".{$chapter_parts[3]}" : '')/*)*/
894
		];
895
	}
896
897 2
	public function getTitleData(string $title_url) {
898 2
		$fullURL = $this->getFullTitleURL($title_url);
899 2
		return $this->parseFoolSlide($fullURL, $title_url);
900
	}
901
}
902