Completed
Push — master ( 4c0e90...a03a76 )
by Angus
02:49
created

Tracker_Title_Model::updateByID()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 15
nc 2
nop 2
dl 0
loc 27
ccs 0
cts 15
cp 0
crap 6
rs 8.8571
c 0
b 0
f 0
1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
class Tracker_Title_Model extends Tracker_Base_Model {
4 127
	public function __construct() {
5 127
		parent::__construct();
6 127
	}
7
8
	/**
9
	 * @param string $titleURL
10
	 * @param int    $siteID
11
	 * @param bool   $create
12
	 * @param bool   $returnData
13
	 *
14
	 * @return array|int
15
	 */
16
	public function getID(string $titleURL, int $siteID, bool $create = TRUE, bool $returnData = FALSE) {
17
		$query = $this->db->select('tracker_titles.id, tracker_titles.title, tracker_titles.title_url, tracker_titles.latest_chapter, tracker_titles.status, tracker_sites.site_class, (tracker_titles.last_checked > DATE_SUB(NOW(), INTERVAL 3 DAY)) AS active', FALSE)
18
		                  ->from('tracker_titles')
19
		                  ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
20
		                  ->where('tracker_titles.title_url', $titleURL)
21
		                  ->where('tracker_titles.site_id', $siteID)
22
		                  ->get();
23
24
		if($query->num_rows() > 0) {
25
			$id = (int) $query->row('id');
26
27
			//This updates inactive series if they are newly added, as noted in https://github.com/DakuTree/manga-tracker/issues/5#issuecomment-247480804
28
			if(((int) $query->row('active')) === 0 && $query->row('status') === 0) {
29
				$siteClass = $query->row('site_class');
30
				$titleData = $this->sites->{$siteClass}->getTitleData($query->row('title_url'));
31
				if(!is_null($titleData['latest_chapter']) || $this->sites->{$siteClass}->canHaveNoChapters) {
32
					if($this->updateByID((int) $id, $titleData['latest_chapter'])) {
33
						//Make sure last_checked is always updated on successful run.
34
						//CHECK: Is there a reason we aren't just doing this in updateTitleById?
35
						$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
36
						         ->where('id', $id)
37
						         ->update('tracker_titles');
38
					}
39
				} else {
40
					log_message('error', "{$siteClass} | {$query->row('title')} ({$query->row('title_url')}) | Failed to update.");
41
				}
42
			}
43
44
			$titleID = $id;
45
		} else {
46
			//TODO: Check if title is valid URL!
47
			if($create) $titleID = $this->addTitle($titleURL, $siteID);
48
		}
49
		if(!isset($titleID) || !$titleID) $titleID = 0;
50
51
		return ($returnData && $titleID !== 0 ? $query->row_array() : $titleID);
52
	}
53
54
	/**
55
	 * @param string $title
56
	 * @param string $siteURL
57
	 *
58
	 * @return array|int
59
	 * @throws Exception
60
	 */
61
	public function getIDFromData(string $title, string $siteURL) {
62
		if(!($siteData = $this->getSiteDataFromURL($siteURL))) {
63
			throw new Exception("Site URL is invalid: {$siteURL}");
64
		}
65
66
		return $this->getID($title, $siteData->id);
67
	}
68
69
	/**
70
	 * @param string $titleURL
71
	 * @param int    $siteID
72
	 *
73
	 * @return int
74
	 */
75
	private function addTitle(string $titleURL, int $siteID) : int {
76
		$query = $this->db->select('site, site_class')
77
		                  ->from('tracker_sites')
78
		                  ->where('id', $siteID)
79
		                  ->get();
80
81
		$titleData = $this->sites->{$query->row()->site_class}->getTitleData($titleURL, TRUE);
82
83
		//FIXME: getTitleData can fail, which will in turn cause the below to fail aswell, we should try and account for that
84
		if($titleData) {
85
			$this->db->insert('tracker_titles', array_merge($titleData, ['title_url' => $titleURL, 'site_id' => $siteID]));
86
			$titleID = $this->db->insert_id();
0 ignored issues
show
Bug introduced by
The method insert_id() does not exist on CI_DB_query_builder. Did you maybe mean insert()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
87
88
			$this->History->updateTitleHistory((int) $titleID, NULL, $titleData['latest_chapter'] ?? NULL, $titleData['last_updated'] ?? date("Y-m-d H:i:s", now()));
89
		} else {
90
			log_message('error', "getTitleData failed for: {$query->row()->site_class} | {$titleURL}");
91
		}
92
		return $titleID ?? 0;
93
	}
94
95
96
	/**
97
	 * @param int    $titleID
98
	 * @param string? $latestChapter
0 ignored issues
show
Documentation introduced by
The doc-type string? could not be parsed: Unknown type name "string?" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
99
	 *
100
	 * @return bool
101
	 */
102
	public function updateByID(int $titleID, ?string $latestChapter) : bool {
103
		//FIXME: Really not too happy with how we're doing history stuff here, it just feels messy.
104
		$query = $this->db->select('latest_chapter AS current_chapter')
105
		                  ->from('tracker_titles')
106
		                  ->where('id', $titleID)
107
		                  ->get();
108
		$row = $query->row();
109
110
		//TODO (CHECK): If failed_checks changes won't that trigger affected_rows?
111
		$success = $this->db->set(['latest_chapter' => $latestChapter, 'failed_checks' => 0]) //last_updated gets updated via a trigger if something changes
112
		                    ->where('id', $titleID)
113
		                    ->update('tracker_titles');
114
115
		if($this->db->affected_rows() > 0) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CI_DB_query_builder as the method affected_rows() does only exist in the following sub-classes of CI_DB_query_builder: CI_DB_cubrid_driver, CI_DB_ibase_driver, CI_DB_mssql_driver, CI_DB_mysql_driver, CI_DB_mysqli_driver, CI_DB_oci8_driver, CI_DB_pdo_4d_driver, CI_DB_pdo_cubrid_driver, CI_DB_pdo_dblib_driver, CI_DB_pdo_driver, CI_DB_pdo_firebird_driver, CI_DB_pdo_ibm_driver, CI_DB_pdo_informix_driver, CI_DB_pdo_mysql_driver, CI_DB_pdo_oci_driver, CI_DB_pdo_odbc_driver, CI_DB_pdo_pgsql_driver, CI_DB_pdo_sqlite_driver, CI_DB_pdo_sqlsrv_driver, CI_DB_postgre_driver, CI_DB_sqlite3_driver, CI_DB_sqlite_driver, CI_DB_sqlsrv_driver. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
116
			//Clear hidden latest chapter
117
			$this->db->set(['ignore_chapter' => 'NULL', 'last_updated' => 'last_updated'], NULL, FALSE)
118
			         ->where('title_id', $titleID)
119
			         ->update('tracker_chapters');
120
		}
121
122
		//Update History
123
		//NOTE: To avoid doing another query to grab the last_updated time, we just use time() which <should> get the same thing.
124
		//FIXME: The <preferable> solution here is we'd just check against the last_updated time, but that can have a few issues.
125
		$this->History->updateTitleHistory($titleID, $row->current_chapter, $latestChapter, date('Y-m-d H:i:s'));
126
127
		return (bool) $success;
128
	}
129
130
	public function updateFailedChecksByID(int $titleID) : bool {
131
		$success = $this->db->set('failed_checks', 'failed_checks + 1', FALSE)
132
		                    ->where('id', $titleID)
133
		                    ->update('tracker_titles');
134
135
		return $success;
136
	}
137
138
	/**
139
	 * @param string $site_url
140
	 *
141
	 * @return stdClass|object|null
142
	 */
143 2
	public function getSiteDataFromURL(string $site_url) {
144 2
		$query = $this->db->select('*')
145 2
		                  ->from('tracker_sites')
146 2
		                  ->where('site', $site_url)
147 2
		                  ->get();
148
149 2
		return $query->row();
150
	}
151
}
152