Passed
Pull Request — master (#5)
by Cody
03:17
created

Af_Readability   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Importance

Changes 9
Bugs 1 Features 1
Metric Value
eloc 133
c 9
b 1
f 1
dl 0
loc 280
rs 8.96
wmc 43

How to fix   Complexity   

Complex Class

Complex classes like Af_Readability 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.

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 Af_Readability, and based on these observations, apply Extract Interface, too.

1
<?php
2
use andreskrey\Readability\Readability;
3
use andreskrey\Readability\Configuration;
4
5
class Af_Readability extends Plugin {
6
7
	/* @var PluginHost $host */
8
	private $host;
9
10
	public function about() {
11
		return array(1.0,
12
			"Try to inline article content using Readability",
13
			"fox");
14
	}
15
16
	public function flags() {
17
		return array("needs_curl" => true);
18
	}
19
20
	public function save() {
21
		$enable_share_anything = checkbox_to_sql_bool($_POST["enable_share_anything"]);
22
23
		$this->host->set($this, "enable_share_anything", $enable_share_anything);
24
25
		echo __("Data saved.");
26
	}
27
28
	public function init($host)
29
	{
30
		$this->host = $host;
31
32
		if (version_compare(PHP_VERSION, '7.0.0', '<')) {
33
			user_error("af_readability requires PHP 7.0", E_USER_WARNING);
34
			return;
35
		}
36
37
		$host->add_hook($host::HOOK_ARTICLE_FILTER, $this);
38
		$host->add_hook($host::HOOK_PREFS_TAB, $this);
39
		$host->add_hook($host::HOOK_PREFS_EDIT_FEED, $this);
40
		$host->add_hook($host::HOOK_PREFS_SAVE_FEED, $this);
41
42
		// Note: we have to install the hook even if disabled because init() is being run before plugin data has loaded
43
		// so we can't check for our storage-set options here
44
		$host->add_hook($host::HOOK_GET_FULL_TEXT, $this);
45
46
		$host->add_filter_action($this, "action_inline", __("Inline content"));
47
	}
48
49
	public function hook_prefs_tab($args) {
50
		if ($args != "prefFeeds") return;
51
52
		print "<div dojoType='dijit.layout.AccordionPane'
53
			title=\"<i class='material-icons'>extension</i> ".__('Readability settings (af_readability)')."\">";
54
55
		if (version_compare(PHP_VERSION, '7.0.0', '<')) {
56
			print_error("This plugin requires PHP 7.0.");
57
		} else {
58
59
			print "<h2>" . __("Global settings") . "</h2>";
60
61
			print_notice("Enable for specific feeds in the feed editor.");
62
63
			print "<form dojoType='dijit.form.Form'>";
64
65
			print "<script type='dojo/method' event='onSubmit' args='evt'>
66
			evt.preventDefault();
67
			if (this.validate()) {
68
				console.log(dojo.objectToQuery(this.getValues()));
69
				new Ajax.Request('backend.php', {
70
					parameters: dojo.objectToQuery(this.getValues()),
71
					onComplete: function(transport) {
72
						Notify.info(transport.responseText);
73
					}
74
				});
75
				//this.reset();
76
			}
77
			</script>";
78
79
			print_hidden("op", "pluginhandler");
80
			print_hidden("method", "save");
81
			print_hidden("plugin", "af_readability");
82
83
			$enable_share_anything = $this->host->get($this, "enable_share_anything");
84
85
			print "<fieldset>";
86
			print "<label class='checkbox'> ";
87
			print_checkbox("enable_share_anything", $enable_share_anything);
88
			print " " . __("Provide full-text services to core code (bookmarklets) and other plugins");
89
			print "</label>";
90
			print "</fieldset>";
91
92
			print_button("submit", __("Save"), "class='alt-primary'");
93
			print "</form>";
94
95
			$enabled_feeds = $this->host->get($this, "enabled_feeds");
96
			if (!is_array($enabled_feeds)) $enabled_feeds = array();
97
98
			$enabled_feeds = $this->filter_unknown_feeds($enabled_feeds);
99
			$this->host->set($this, "enabled_feeds", $enabled_feeds);
100
101
			if (count($enabled_feeds) > 0) {
102
				print "<h3>" . __("Currently enabled for (click to edit):") . "</h3>";
103
104
				print "<ul class='panel panel-scrollable list list-unstyled'>";
105
				foreach ($enabled_feeds as $f) {
106
					print "<li><i class='material-icons'>rss_feed</i> <a href='#'
107
						onclick='CommonDialogs.editFeed($f)'>".
108
						Feeds::getFeedTitle($f) . "</a></li>";
109
				}
110
				print "</ul>";
111
			}
112
113
		}
114
115
		print "</div>";
116
	}
117
118
	public function hook_prefs_edit_feed($feed_id) {
119
		print "<header>".__("Readability")."</header>";
120
		print "<section>";
121
122
		$enabled_feeds = $this->host->get($this, "enabled_feeds");
123
		if (!is_array($enabled_feeds)) $enabled_feeds = array();
124
125
		$key = array_search($feed_id, $enabled_feeds);
126
		$checked = $key !== FALSE ? "checked" : "";
127
128
		print "<fieldset>";
129
130
		print "<label class='checkbox'><input dojoType='dijit.form.CheckBox' type='checkbox' id='af_readability_enabled'
131
			name='af_readability_enabled' $checked>&nbsp;".__('Inline article content')."</label>";
132
133
		print "</fieldset>";
134
135
		print "</section>";
136
	}
137
138
	public function hook_prefs_save_feed($feed_id) {
139
		$enabled_feeds = $this->host->get($this, "enabled_feeds");
140
		if (!is_array($enabled_feeds)) $enabled_feeds = array();
141
142
		$enable = checkbox_to_sql_bool($_POST["af_readability_enabled"]);
143
		$key = array_search($feed_id, $enabled_feeds);
144
145
		if ($enable) {
146
			if ($key === FALSE) {
147
				array_push($enabled_feeds, $feed_id);
148
			}
149
		} else {
150
			if ($key !== FALSE) {
151
				unset($enabled_feeds[$key]);
152
			}
153
		}
154
155
		$this->host->set($this, "enabled_feeds", $enabled_feeds);
156
	}
157
158
	/**
159
	 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
160
	 */
161
	public function hook_article_filter_action($article, $action) {
162
		return $this->process_article($article);
163
	}
164
165
	public function extract_content($url) {
166
167
		global $fetch_effective_url;
168
169
		$tmp = fetch_file_contents([
170
			"url" => $url,
171
			"http_accept" => "text/*",
172
			"type" => "text/html"]);
173
174
		if ($tmp && mb_strlen($tmp) < 1024 * 500) {
175
			$tmpdoc = new DOMDocument("1.0", "UTF-8");
176
177
			if (!@$tmpdoc->loadHTML($tmp))
178
				return false;
179
180
			// this is the worst hack yet :(
181
			if (strtolower($tmpdoc->encoding) != 'utf-8') {
182
				$tmp = preg_replace("/<meta.*?charset.*?\/?>/i", "", $tmp);
183
				if (empty($tmpdoc->encoding)) {
184
					$tmp = mb_convert_encoding($tmp, 'utf-8');
185
				} else {
186
					$tmp = mb_convert_encoding($tmp, 'utf-8', $tmpdoc->encoding);
187
				}
188
			}
189
190
			try {
191
				$r = new Readability(new Configuration());
192
193
				if ($r->parse($tmp)) {
194
195
					$tmpxpath = new DOMXPath($r->getDOMDOcument());
196
					$entries = $tmpxpath->query('(//a[@href]|//img[@src])');
197
198
					foreach ($entries as $entry) {
199
						if ($entry->hasAttribute("href")) {
200
							$entry->setAttribute("href",
201
									rewrite_relative_url($fetch_effective_url, $entry->getAttribute("href")));
202
203
						}
204
205
						if ($entry->hasAttribute("src")) {
206
							$entry->setAttribute("src",
207
									rewrite_relative_url($fetch_effective_url, $entry->getAttribute("src")));
208
209
						}
210
					}
211
212
					return $r->getContent();
213
				}
214
215
			} catch (Exception $e) {
216
				return false;
217
			}
218
		}
219
220
		return false;
221
	}
222
223
	public function process_article($article) {
224
225
		$extracted_content = $this->extract_content($article["link"]);
226
227
		# let's see if there's anything of value in there
228
		$content_test = trim(strip_tags(sanitize($extracted_content)));
229
230
		if ($content_test) {
231
			$article["content"] = $extracted_content;
232
		}
233
234
		return $article;
235
	}
236
237
	public function hook_article_filter($article) {
238
239
		$enabled_feeds = $this->host->get($this, "enabled_feeds");
240
		if (!is_array($enabled_feeds)) return $article;
241
242
		$key = array_search($article["feed"]["id"], $enabled_feeds);
243
		if ($key === FALSE) return $article;
244
245
		return $this->process_article($article);
246
247
	}
248
249
	public function hook_get_full_text($link)
250
	{
251
		$enable_share_anything = $this->host->get($this, "enable_share_anything");
252
253
		if ($enable_share_anything) {
254
			$extracted_content = $this->extract_content($link);
255
256
			# let's see if there's anything of value in there
257
			$content_test = trim(strip_tags(sanitize($extracted_content)));
258
259
			if ($content_test) {
260
				return $extracted_content;
261
			}
262
		}
263
264
		return false;
265
	}
266
267
	public function api_version() {
268
		return 2;
269
	}
270
271
	private function filter_unknown_feeds($enabled_feeds) {
272
		$tmp = array();
273
274
		foreach ($enabled_feeds as $feed) {
275
276
			$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?");
277
			$sth->execute([$feed, $_SESSION['uid']]);
278
279
			if ($row = $sth->fetch()) {
280
				array_push($tmp, $feed);
281
			}
282
		}
283
284
		return $tmp;
285
	}
286
287
}
288