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") { |
||||||||
51 | return; |
||||||||
52 | } |
||||||||
53 | |||||||||
54 | print "<div dojoType='dijit.layout.AccordionPane' |
||||||||
55 | title=\"<i class='material-icons'>extension</i> ".__('Readability settings (af_readability)')."\">"; |
||||||||
56 | |||||||||
57 | if (version_compare(PHP_VERSION, '7.0.0', '<')) { |
||||||||
58 | print_error("This plugin requires PHP 7.0."); |
||||||||
0 ignored issues
–
show
The function
print_error() has been deprecated: Use twig function errorMessage
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||||
59 | } else { |
||||||||
60 | |||||||||
61 | print "<h2>".__("Global settings")."</h2>"; |
||||||||
62 | |||||||||
63 | print_notice("Enable for specific feeds in the feed editor."); |
||||||||
0 ignored issues
–
show
The call to
print_notice() has too many arguments starting with 'Enable for specific feeds in the feed editor.' .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() The function
print_notice() has been deprecated: Use twig function noticeMessage
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||||
64 | |||||||||
65 | print "<form dojoType='dijit.form.Form'>"; |
||||||||
66 | |||||||||
67 | print "<script type='dojo/method' event='onSubmit' args='evt'> |
||||||||
68 | evt.preventDefault(); |
||||||||
69 | if (this.validate()) { |
||||||||
70 | console.log(dojo.objectToQuery(this.getValues())); |
||||||||
71 | new Ajax.Request('backend.php', { |
||||||||
72 | parameters: dojo.objectToQuery(this.getValues()), |
||||||||
73 | onComplete: function(transport) { |
||||||||
74 | Notify.info(transport.responseText); |
||||||||
75 | } |
||||||||
76 | }); |
||||||||
77 | //this.reset(); |
||||||||
78 | } |
||||||||
79 | </script>"; |
||||||||
80 | |||||||||
81 | print_hidden("op", "pluginhandler"); |
||||||||
82 | print_hidden("method", "save"); |
||||||||
83 | print_hidden("plugin", "af_readability"); |
||||||||
84 | |||||||||
85 | $enable_share_anything = $this->host->get($this, "enable_share_anything"); |
||||||||
86 | |||||||||
87 | print "<fieldset>"; |
||||||||
88 | print "<label class='checkbox'> "; |
||||||||
89 | print_checkbox("enable_share_anything", $enable_share_anything); |
||||||||
90 | print " ".__("Provide full-text services to core code (bookmarklets) and other plugins"); |
||||||||
91 | print "</label>"; |
||||||||
92 | print "</fieldset>"; |
||||||||
93 | |||||||||
94 | print_button("submit", __("Save"), "class='alt-primary'"); |
||||||||
95 | print "</form>"; |
||||||||
96 | |||||||||
97 | $enabled_feeds = $this->host->get($this, "enabled_feeds"); |
||||||||
98 | if (!is_array($enabled_feeds)) { |
||||||||
99 | $enabled_feeds = array(); |
||||||||
100 | } |
||||||||
101 | |||||||||
102 | $enabled_feeds = $this->filter_unknown_feeds($enabled_feeds); |
||||||||
103 | $this->host->set($this, "enabled_feeds", $enabled_feeds); |
||||||||
104 | |||||||||
105 | if (count($enabled_feeds) > 0) { |
||||||||
106 | print "<h3>".__("Currently enabled for (click to edit):")."</h3>"; |
||||||||
107 | |||||||||
108 | print "<ul class='panel panel-scrollable list list-unstyled'>"; |
||||||||
109 | foreach ($enabled_feeds as $f) { |
||||||||
110 | print "<li><i class='material-icons'>rss_feed</i> <a href='#' |
||||||||
111 | onclick='CommonDialogs.editFeed($f)'>". |
||||||||
112 | Feeds::getFeedTitle($f)."</a></li>"; |
||||||||
113 | } |
||||||||
114 | print "</ul>"; |
||||||||
115 | } |
||||||||
116 | |||||||||
117 | } |
||||||||
118 | |||||||||
119 | print "</div>"; |
||||||||
120 | } |
||||||||
121 | |||||||||
122 | public function hook_prefs_edit_feed($feed_id) { |
||||||||
123 | print "<header>".__("Readability")."</header>"; |
||||||||
124 | print "<section>"; |
||||||||
125 | |||||||||
126 | $enabled_feeds = $this->host->get($this, "enabled_feeds"); |
||||||||
127 | if (!is_array($enabled_feeds)) { |
||||||||
128 | $enabled_feeds = array(); |
||||||||
129 | } |
||||||||
130 | |||||||||
131 | $key = array_search($feed_id, $enabled_feeds); |
||||||||
132 | $checked = $key !== false ? "checked" : ""; |
||||||||
133 | |||||||||
134 | print "<fieldset>"; |
||||||||
135 | |||||||||
136 | print "<label class='checkbox'><input dojoType='dijit.form.CheckBox' type='checkbox' id='af_readability_enabled' |
||||||||
137 | name='af_readability_enabled' $checked> ".__('Inline article content')."</label>"; |
||||||||
138 | |||||||||
139 | print "</fieldset>"; |
||||||||
140 | |||||||||
141 | print "</section>"; |
||||||||
142 | } |
||||||||
143 | |||||||||
144 | public function hook_prefs_save_feed($feed_id) { |
||||||||
145 | $enabled_feeds = $this->host->get($this, "enabled_feeds"); |
||||||||
146 | if (!is_array($enabled_feeds)) { |
||||||||
147 | $enabled_feeds = array(); |
||||||||
148 | } |
||||||||
149 | |||||||||
150 | $enable = checkbox_to_sql_bool($_POST["af_readability_enabled"]); |
||||||||
151 | $key = array_search($feed_id, $enabled_feeds); |
||||||||
152 | |||||||||
153 | if ($enable) { |
||||||||
154 | if ($key === false) { |
||||||||
155 | array_push($enabled_feeds, $feed_id); |
||||||||
156 | } |
||||||||
157 | } else { |
||||||||
158 | if ($key !== false) { |
||||||||
159 | unset($enabled_feeds[$key]); |
||||||||
160 | } |
||||||||
161 | } |
||||||||
162 | |||||||||
163 | $this->host->set($this, "enabled_feeds", $enabled_feeds); |
||||||||
164 | } |
||||||||
165 | |||||||||
166 | /** |
||||||||
167 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
||||||||
168 | */ |
||||||||
169 | public function hook_article_filter_action($article, $action) { |
||||||||
0 ignored issues
–
show
The parameter
$action is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||||
170 | return $this->process_article($article); |
||||||||
171 | } |
||||||||
172 | |||||||||
173 | public function extract_content($url) { |
||||||||
174 | |||||||||
175 | global $fetch_effective_url; |
||||||||
176 | |||||||||
177 | $tmp = fetch_file_contents([ |
||||||||
178 | "url" => $url, |
||||||||
179 | "http_accept" => "text/*", |
||||||||
180 | "type" => "text/html"]); |
||||||||
181 | |||||||||
182 | if ($tmp && mb_strlen($tmp) < 1024 * 500) { |
||||||||
0 ignored issues
–
show
It seems like
$tmp can also be of type true ; however, parameter $str of mb_strlen() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||
183 | $tmpdoc = new DOMDocument("1.0", "UTF-8"); |
||||||||
184 | |||||||||
185 | if (!@$tmpdoc->loadHTML($tmp)) { |
||||||||
0 ignored issues
–
show
It seems like
$tmp can also be of type true ; however, parameter $source of DOMDocument::loadHTML() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||||
186 | return false; |
||||||||
187 | } |
||||||||
188 | |||||||||
189 | // this is the worst hack yet :( |
||||||||
190 | if (strtolower($tmpdoc->encoding) != 'utf-8') { |
||||||||
191 | $tmp = preg_replace("/<meta.*?charset.*?\/?>/i", "", $tmp); |
||||||||
192 | if (empty($tmpdoc->encoding)) { |
||||||||
193 | $tmp = mb_convert_encoding($tmp, 'utf-8'); |
||||||||
194 | } else { |
||||||||
195 | $tmp = mb_convert_encoding($tmp, 'utf-8', $tmpdoc->encoding); |
||||||||
196 | } |
||||||||
197 | } |
||||||||
198 | |||||||||
199 | try { |
||||||||
200 | $r = new Readability(new Configuration()); |
||||||||
201 | |||||||||
202 | if ($r->parse($tmp)) { |
||||||||
203 | |||||||||
204 | $tmpxpath = new DOMXPath($r->getDOMDOcument()); |
||||||||
205 | $entries = $tmpxpath->query('(//a[@href]|//img[@src])'); |
||||||||
206 | |||||||||
207 | foreach ($entries as $entry) { |
||||||||
208 | if ($entry->hasAttribute("href")) { |
||||||||
209 | $entry->setAttribute("href", |
||||||||
210 | rewrite_relative_url($fetch_effective_url, $entry->getAttribute("href"))); |
||||||||
211 | |||||||||
212 | } |
||||||||
213 | |||||||||
214 | if ($entry->hasAttribute("src")) { |
||||||||
215 | $entry->setAttribute("src", |
||||||||
216 | rewrite_relative_url($fetch_effective_url, $entry->getAttribute("src"))); |
||||||||
217 | |||||||||
218 | } |
||||||||
219 | } |
||||||||
220 | |||||||||
221 | return $r->getContent(); |
||||||||
222 | } |
||||||||
223 | |||||||||
224 | } catch (Exception $e) { |
||||||||
225 | return false; |
||||||||
226 | } |
||||||||
227 | } |
||||||||
228 | |||||||||
229 | return false; |
||||||||
230 | } |
||||||||
231 | |||||||||
232 | public function process_article($article) { |
||||||||
233 | |||||||||
234 | $extracted_content = $this->extract_content($article["link"]); |
||||||||
235 | |||||||||
236 | # let's see if there's anything of value in there |
||||||||
237 | $content_test = trim(strip_tags(sanitize($extracted_content))); |
||||||||
238 | |||||||||
239 | if ($content_test) { |
||||||||
240 | $article["content"] = $extracted_content; |
||||||||
241 | } |
||||||||
242 | |||||||||
243 | return $article; |
||||||||
244 | } |
||||||||
245 | |||||||||
246 | public function hook_article_filter($article) { |
||||||||
247 | |||||||||
248 | $enabled_feeds = $this->host->get($this, "enabled_feeds"); |
||||||||
249 | if (!is_array($enabled_feeds)) { |
||||||||
250 | return $article; |
||||||||
251 | } |
||||||||
252 | |||||||||
253 | $key = array_search($article["feed"]["id"], $enabled_feeds); |
||||||||
254 | if ($key === false) { |
||||||||
255 | return $article; |
||||||||
256 | } |
||||||||
257 | |||||||||
258 | return $this->process_article($article); |
||||||||
259 | |||||||||
260 | } |
||||||||
261 | |||||||||
262 | public function hook_get_full_text($link) |
||||||||
263 | { |
||||||||
264 | $enable_share_anything = $this->host->get($this, "enable_share_anything"); |
||||||||
265 | |||||||||
266 | if ($enable_share_anything) { |
||||||||
267 | $extracted_content = $this->extract_content($link); |
||||||||
268 | |||||||||
269 | # let's see if there's anything of value in there |
||||||||
270 | $content_test = trim(strip_tags(sanitize($extracted_content))); |
||||||||
271 | |||||||||
272 | if ($content_test) { |
||||||||
273 | return $extracted_content; |
||||||||
274 | } |
||||||||
275 | } |
||||||||
276 | |||||||||
277 | return false; |
||||||||
278 | } |
||||||||
279 | |||||||||
280 | public function api_version() { |
||||||||
281 | return 2; |
||||||||
282 | } |
||||||||
283 | |||||||||
284 | private function filter_unknown_feeds($enabled_feeds) { |
||||||||
285 | $tmp = array(); |
||||||||
286 | |||||||||
287 | foreach ($enabled_feeds as $feed) { |
||||||||
288 | |||||||||
289 | $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); |
||||||||
290 | $sth->execute([$feed, $_SESSION['uid']]); |
||||||||
291 | |||||||||
292 | if ($row = $sth->fetch()) { |
||||||||
0 ignored issues
–
show
|
|||||||||
293 | array_push($tmp, $feed); |
||||||||
294 | } |
||||||||
295 | } |
||||||||
296 | |||||||||
297 | return $tmp; |
||||||||
298 | } |
||||||||
299 | |||||||||
300 | } |
||||||||
301 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.