Complex classes like Tracker_Admin_Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Tracker_Admin_Model, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed'); | 
            ||
| 3 | class Tracker_Admin_Model extends Tracker_Base_Model { | 
            ||
| 4 | 127 | 	public function __construct() { | 
            |
| 7 | |||
| 8 | /**  | 
            ||
| 9 | * Checks for any series that haven't updated in 16 hours and updates them.  | 
            ||
| 10 | * This is ran every 4 hours via a cron job.  | 
            ||
| 11 | */  | 
            ||
| 12 | 	public function updateLatestChapters() { | 
            ||
| 13 | // @formatter:off  | 
            ||
| 14 | $query = $this->db  | 
            ||
| 15 | 			->select(' | 
            ||
| 16 | tracker_titles.id as title_id,  | 
            ||
| 17 | tracker_titles.title,  | 
            ||
| 18 | tracker_titles.title_url,  | 
            ||
| 19 | tracker_titles.status,  | 
            ||
| 20 | tracker_sites.site,  | 
            ||
| 21 | tracker_sites.site_class,  | 
            ||
| 22 | tracker_sites.status,  | 
            ||
| 23 | tracker_titles.latest_chapter,  | 
            ||
| 24 | tracker_titles.last_updated,  | 
            ||
| 25 | from_unixtime(MAX(auth_users.last_login)) AS timestamp  | 
            ||
| 26 | ')  | 
            ||
| 27 | 			->from('tracker_titles') | 
            ||
| 28 | 			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') | 
            ||
| 29 | 			->join('tracker_chapters', 'tracker_titles.id = tracker_chapters.title_id', 'left') | 
            ||
| 30 | 			->join('auth_users', 'tracker_chapters.user_id = auth_users.id', 'left') | 
            ||
| 31 | 			->where('tracker_sites.status', 'enabled') | 
            ||
| 32 | ->group_start()  | 
            ||
| 33 | //Check if title is marked as on-going...  | 
            ||
| 34 | 				->where('tracker_titles.status', 0) | 
            ||
| 35 | //AND matches one of where queries below  | 
            ||
| 36 | ->group_start()  | 
            ||
| 37 | //Then check if it's NULL (only occurs for new series)  | 
            ||
| 38 | 					->where('latest_chapter', NULL) | 
            ||
| 39 | //OR if it hasn't updated within the past 12 hours AND isn't a custom update site  | 
            ||
| 40 | ->or_group_start()  | 
            ||
| 41 | 						->where('tracker_sites.use_custom', 'N') | 
            ||
| 42 | 						->where('last_checked < DATE_SUB(NOW(), INTERVAL 12 HOUR)') | 
            ||
| 43 | ->group_end()  | 
            ||
| 44 | //OR it is a custom update site, has more than one follower and hasn't updated within the past 72 hours.  | 
            ||
| 45 | ->or_group_start()  | 
            ||
| 46 | 						->where('tracker_titles.id IN ( | 
            ||
| 47 | SELECT title_id  | 
            ||
| 48 | FROM tracker_chapters  | 
            ||
| 49 | GROUP BY title_id  | 
            ||
| 50 | HAVING COUNT(title_id) > 1  | 
            ||
| 51 | )', NULL, FALSE)  | 
            ||
| 52 | |||
| 53 | 						->where('last_checked < DATE_SUB(NOW(), INTERVAL 72 HOUR)') | 
            ||
| 54 | ->group_end()  | 
            ||
| 55 | //OR it is a custom update site and hasn't updated within the past 120 hours (5 days)  | 
            ||
| 56 | 					->or_where('last_checked < DATE_SUB(NOW(), INTERVAL 120 HOUR)') | 
            ||
| 57 | ->group_end()  | 
            ||
| 58 | ->group_end()  | 
            ||
| 59 | ->or_group_start()  | 
            ||
| 60 | //Check if title is marked as complete...  | 
            ||
| 61 | 				->where('tracker_titles.status', 1) | 
            ||
| 62 | //Then check if it hasn't updated within the past week  | 
            ||
| 63 | 				->where('last_checked < DATE_SUB(NOW(), INTERVAL 1 WEEK)') | 
            ||
| 64 | ->group_end()  | 
            ||
| 65 | //Status 2 (One-shot) & 255 (Ignore) are both not updated intentionally.  | 
            ||
| 66 | 			->group_by('tracker_titles.id, tracker_chapters.active') | 
            ||
| 67 | //Check if the series is actually being tracked by someone  | 
            ||
| 68 | 			->having('timestamp IS NOT NULL') | 
            ||
| 69 | //AND if it's currently marked as active by the user  | 
            ||
| 70 | 			->having('tracker_chapters.active', 'Y') | 
            ||
| 71 | //AND if they have been active in the last 120 hours (5 days)  | 
            ||
| 72 | 			->having('timestamp > DATE_SUB(NOW(), INTERVAL 120 HOUR)') | 
            ||
| 73 | 			->order_by('tracker_titles.title', 'ASC') | 
            ||
| 74 | ->get();  | 
            ||
| 75 | // @formatter:on  | 
            ||
| 76 | |||
| 77 | 		if($query->num_rows() > 0) { | 
            ||
| 78 | 			foreach ($query->result() as $row) { | 
            ||
| 79 | 				print "> {$row->title} <{$row->site_class} - {$row->title_url}> | <{$row->title_id}>"; //Print this prior to doing anything so we can more easily find out if something went wrong | 
            ||
| 80 | 				$titleData = $this->sites->{$row->site_class}->getTitleData($row->title_url); | 
            ||
| 81 | 				if(is_array($titleData) && (!is_null($titleData['latest_chapter']) || $this->sites->{$row->site_class}->canHaveNoChapters)) { | 
            ||
| 82 | 					if(count($titleData) === 3) { | 
            ||
| 83 | // Normal update.  | 
            ||
| 84 | |||
| 85 | //FIXME: "At the moment" we don't seem to be doing anything with TitleData['last_updated'].  | 
            ||
| 86 | // Should we even use this? Y/N  | 
            ||
| 87 | 						if($this->Tracker->title->updateByID((int) $row->title_id, $titleData['latest_chapter'])) { | 
            ||
| 88 | //Make sure last_checked is always updated on successful run.  | 
            ||
| 89 | //CHECK: Is there a reason we aren't just doing this in updateByID?  | 
            ||
| 90 | 							$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) | 
            ||
| 91 | 							         ->where('id', $row->title_id) | 
            ||
| 92 | 							         ->update('tracker_titles'); | 
            ||
| 93 | |||
| 94 | 							print " - ({$titleData['latest_chapter']})\n"; | 
            ||
| 95 | 						} else { | 
            ||
| 96 | 							log_message('error', "{$row->title} failed to update successfully"); | 
            ||
| 97 | |||
| 98 | print " - Something went wrong?\n";  | 
            ||
| 99 | }  | 
            ||
| 100 | 					} else { | 
            ||
| 101 | // No chapters were returned, but site allows this.  | 
            ||
| 102 | 						if($this->Tracker->title->updateByID((int) $row->title_id, NULL)) { | 
            ||
| 103 | //Make sure last_checked is always updated on successful run.  | 
            ||
| 104 | //CHECK: Is there a reason we aren't just doing this in updateByID?  | 
            ||
| 105 | 							$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) | 
            ||
| 106 | 							         ->where('id', $row->title_id) | 
            ||
| 107 | 							         ->update('tracker_titles'); | 
            ||
| 108 | |||
| 109 | print " - (No chapters found?)\n";  | 
            ||
| 110 | 						} else { | 
            ||
| 111 | 							log_message('error', "{$row->title} failed to update successfully"); | 
            ||
| 112 | |||
| 113 | print " - Something went wrong?\n";  | 
            ||
| 114 | }  | 
            ||
| 115 | }  | 
            ||
| 116 | 				} else { | 
            ||
| 117 | 					log_message('error', "{$row->title} failed to update successfully"); | 
            ||
| 118 | $this->Tracker->title->updateFailedChecksByID((int) $row->title_id);  | 
            ||
| 119 | |||
| 120 | print " - FAILED TO PARSE\n";  | 
            ||
| 121 | }  | 
            ||
| 122 | }  | 
            ||
| 123 | }  | 
            ||
| 124 | }  | 
            ||
| 125 | |||
| 126 | /**  | 
            ||
| 127 | * Intended to be only used as a quick way to update all series on a site after a bug.  | 
            ||
| 128 | *  | 
            ||
| 129 | * @param string $site  | 
            ||
| 130 | * @param null|string $last_checked  | 
            ||
| 131 | */  | 
            ||
| 132 | 	public function updateAllTitlesBySite(string $site, ?string $last_checked = NULL) { | 
            ||
| 203 | |||
| 204 | /**  | 
            ||
| 205 | * Checks for any sites which support custom updating (usually via following lists) and updates them.  | 
            ||
| 206 | * This is run hourly.  | 
            ||
| 207 | */  | 
            ||
| 208 | 	public function updateCustom() { | 
            ||
| 257 | |||
| 258 | 	public function refollowCustom() { | 
            ||
| 291 | |||
| 292 | /**  | 
            ||
| 293 | * Checks every series to see if title has changed, and update if so.  | 
            ||
| 294 | * This is ran once a month via a cron job  | 
            ||
| 295 | */  | 
            ||
| 296 | 	public function updateTitles() { | 
            ||
| 347 | |||
| 348 | 	public function incrementRequests() : void { | 
            ||
| 369 | |||
| 370 | 	public function getNextUpdateTime(string $format = "%H:%I:%S") : string { | 
            ||
| 403 | }  | 
            ||
| 404 |