PluginProbe
avalex – Automatisch sichere Rechtstexte / 1.5.7
avalex – Automatisch sichere Rechtstexte v1.5.7
trunk 1.5.6 1.5.7 1.5.8 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 3.1.4
avalex / avalex.php

avalex.php in avalex – Automatisch sichere Rechtstexte 1.5.7, at avalex.php

550 lines 17.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: avalex
4 Description: Das Plugin ermöglicht es, Ihre Website mit der avalex API zu verbinden. Einen API Key erhalten Sie auf www.avalex.de.
5 Author: avalex GmbH
6 Author URI: https://avalex.de/
7 Version: 1.5.7
8 Text Domain: Avalex
9 Domain Path: /languages
10 */
11
12 class Avalex {
13 protected $apiUrl = 'https://avalex.de';
14 protected $fallbackApiUrl = 'https://proxy.avalex.de';
15
16 public function __construct() {
17 // Call the functions that handle the stuff we need to do on plugin activation.
18 register_activation_hook(__FILE__, array($this, 'activatePlugin'));
19
20 // And call the functions, when the user deactivates the plugin.
21 register_deactivation_hook(__FILE__, array($this, 'deactivatePlugin'));
22
23 // And finally call the functions, when the user deletes the plugin.
24 register_uninstall_hook(__FILE__, 'Avalex::uninstallPlugin');
25
26 // We also want to load the language files (To make WordPress think it is translated).
27 $this->loadLanguageFiles();
28
29 // Set our variables.
30 global $wpdb;
31 $this->pluginPath = dirname(__FILE__);
32 $this->tableName = $wpdb->prefix . 'avalex';
33 $this->apiKey = get_option('avalex_api_key', false);
34 $this->isKeyValid = get_option('avalex_valid_api_key', false);
35 $this->isDomainValid = false;
36 $this->response = false;
37 $this->dseHtml = $this->getDseFromDatabase();
38 $this->recursiveCalls = 0;
39 $this->notice = false;
40 $this->cachePath = WP_CONTENT_DIR . '/cache';
41 $this->avalexCronAction();
42
43 // Register a new cron schedule.
44 add_filter('cron_schedules', array($this, 'addQuarterlyCronSchedule'));
45
46 // Call our methods that we need as early as possible.
47 $this->init();
48
49 // Add our admin menu page.
50 add_action('admin_menu', array($this, 'addAdminMenu'));
51
52 // Add our shortcode.
53 add_shortcode('avalex', array($this, 'renderAvalexShortcode'));
54
55 // Add update functionality.
56 add_action('pre_set_site_transient_update_plugins', array($this, 'pluginUpdateNotification'));
57
58 // We need an action to make it possible to force a DSE update from outside for important updates that can't wait.
59 add_action('init', array($this, 'forceUpdate'));
60
61 // Add settings link to plugin page.
62 add_action('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'addSettingsLink'));
63 }
64
65 public function activatePlugin() {
66 // $this->checkPhpVersion();
67 $this->createTable();
68 $this->maybeDeleteOldCronJob();
69 $this->registerCronJob();
70 }
71
72 public function checkPhpVersion() {
73 if (version_compare(PHP_VERSION, '5.6', '>')) {
74 return;
75 }
76
77 wp_die('PHP Version zu alt. Bitte nutzen Sie mindestens PHP 5.6.<br>Sie nutzen aktuell Version: ' . PHP_VERSION);
78 }
79
80 public function createTable() {
81 global $wpdb;
82 $charsetCollate = $wpdb->get_charset_collate();
83
84 // First we want to drop any old tables of avalex, if there are some.
85 $this->deleteAvalexTable();
86
87 // Now create our new table.
88 $sql = "CREATE TABLE $this->tableName (
89 id mediumint(9) NOT NULL AUTO_INCREMENT,
90 time datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
91 data longtext NOT NULL,
92 PRIMARY KEY (id)
93 ) $charsetCollate;";
94
95 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
96 dbDelta($sql);
97 }
98
99 public function maybeDeleteOldCronJob() {
100 if (wp_next_scheduled('avalex_cron_event')) {
101 wp_clear_scheduled_hook('avalex_cron_event');
102 }
103
104 if(wp_next_scheduled('avalex_update_dse_cron_event')) {
105 wp_clear_scheduled_hook('avalex_update_dse_cron_event');
106 }
107 }
108
109 public function registerCronJob() {
110 if (!wp_next_scheduled('avalex_update_dse_cron_event')) {
111 wp_schedule_event(time(), 'avalex_interval', 'avalex_update_dse_cron_event');
112 }
113 }
114
115 public function deactivatePlugin() {
116 $this->maybeDeleteOldCronJob();
117 }
118
119 public function uninstallPlugin() {
120 global $wpdb;
121 $tableName = $wpdb->prefix . 'avalex';
122 $wpdb->query("DROP TABLE IF EXISTS $tableName");
123
124 self::deleteOptions();
125 }
126
127 public function deleteAvalexTable() {
128 global $wpdb;
129 $wpdb->query("DROP TABLE IF EXISTS $this->tableName");
130 }
131
132 public function deleteOptions() {
133 delete_option('avalex_api_key');
134 delete_option('avalex_valid_api_key');
135 }
136
137 public function avalexCronAction() {
138 add_action('avalex_update_dse_cron_event', array($this, 'fetchAvalexDse'));
139 }
140
141 public function addQuarterlyCronSchedule($schedules) {
142 $schedules['avalex_interval'] = array(
143 'interval' => 21600,
144 'display' => __('4 times a day'));
145 return $schedules;
146 }
147
148 public function loadLanguageFiles() {
149 load_plugin_textdomain( 'Avalex', FALSE, basename( dirname( __FILE__ ) ) . '/languages/' );
150 }
151
152 public function init() {
153 // If the user hits the submit button, we want to store the new apikey.
154 if (isset($_POST['save_avalex'])) {
155 if (!$this->saveApiKey()) {
156 return;
157 }
158
159 if (!$this->validateApiKey()) {
160
161 return;
162 }
163
164 if (!$this->validateDomain()) {
165 return;
166 }
167
168 $this->fetchAvalexDse();
169 }
170 }
171
172 public function addAdminMenu() {
173 add_options_page('avalex', 'avalex', 'manage_options', 'avalex', array($this, 'avalexAdminPage'));
174 }
175
176 public function avalexAdminPage() {
177 include $this->pluginPath . '/templates/admin_page.php';
178 }
179
180 public function addNotice() {
181 if (!$this->notice) {
182 return false;
183 }
184
185 echo '<div class="notice notice-' . $this->noticeType . '"><p>' . $this->noticeMessage . '</p></div>';
186 return true;
187 }
188
189 private function saveApiKey() {
190 // Check if api key was entered.
191 $apiKey = sanitize_text_field($_POST['avalex_api_key']);
192 update_option('avalex_api_key', $apiKey);
193
194 if (!$apiKey) {
195 $this->notice = true;
196 $this->noticeType = 'error';
197 $this->noticeMessage = __('Please enter an API key.', 'Avalex');
198 return false;
199 }
200
201 if ($apiKey) {
202 $this->apiKey = $apiKey;
203 return true;
204 }
205 }
206
207 private function showApiKey() {
208 if ($this->apiKey) {
209 echo $this->apiKey;
210 return;
211 }
212
213 return false;
214 }
215
216 private function validateApiKey() {
217
218 // Build the api url.
219 $apiUrlDomain = $this->apiUrl . '/api_keys/is_configured.json';
220 if ($this->recursiveCalls == 1) {
221 $apiUrlDomain = $this->fallbackApiUrl . '/api_keys/is_configured.json';
222 }
223
224 $apiUrlDomain = add_query_arg('apikey', $this->apiKey, $apiUrlDomain);
225
226 // Call the api.
227 $response = wp_remote_get($apiUrlDomain, array('timeout' => 1));
228 $responseCode = wp_remote_retrieve_response_code($response);
229
230 // If we have a 401, the key is not valid.
231 if ($responseCode == 401) {
232 $this->notice = true;
233 $this->noticeType = 'error';
234 $this->noticeMessage = 'Der API-Key ist ungültig.';
235 $this->isKeyValid = false;
236 update_option('avalex_valid_api_key', false);
237 return false;
238 }
239
240 // 200 means api key is valid, so we can grab the domain and proceed.
241 if ($responseCode == 200) {
242 update_option('avalex_valid_api_key', true);
243 $this->isKeyValid = 1;
244 $this->response = json_decode(wp_remote_retrieve_body($response), true);
245 $this->recursiveCalls = 0;
246 return true;
247 }
248
249 if (is_wp_error($response)) {
250 // When this is the first time the error happens, we want to try the fallback url.
251 if ($this->recursiveCalls == 0) {
252 $this->recursiveCalls = 1;
253 return $this->validateApiKey();
254 }
255
256 $errorMessage = $response->get_error_message();
257 $this->notice = true;
258 $this->noticeType = 'error';
259 $this->noticeMessage = 'Beim Datenabgleich mit dem Avalex Server ist etwas schiefgelaufen. Bitte wenden Sie sich an den Support mit den folgenden Informationen:<br>' . $errorMessage;
260 return false;
261 }
262
263 if ($responseCode == 400) {
264 // Website not configured.
265 $this->notice = true;
266 $this->noticeType = 'error';
267 $this->noticeMessage = 'Webseite noch nicht fertig konfiguriert. Bitte loggen Sie sich bei Avalex ein und schließen die Konfiguration ab.';
268 $this->isKeyValid = 1;
269 update_option('avalex_valid_api_key', true);
270 return false;
271 }
272
273
274 update_option('avalex_valid_api_key', false);
275 return false;
276 }
277
278 private function validateDomain() {
279 $wordPressUrl = $this->trimDomain(home_url());
280 $avalexDomain = $this->trimDomain($this->response['domain']);
281
282 if ($wordPressUrl != $avalexDomain) {
283 $this->isKeyValid = false;
284 $this->notice = true;
285 $this->noticeType = 'error';
286 $this->noticeMessage = 'Die aktuelle Domain des Servers (' . home_url() . ') stimmt nicht mit der Domain überein, die Sie in avalex eingegeben haben.';
287 return false;
288 }
289
290 // Domain is valid, so set the internal state.
291 $this->isDomainValid = true;
292 return true;
293 }
294
295 public function trimDomain($domain) {
296 // Remove protocoll and www.
297 $domain = str_replace('http://', '', $domain);
298 $domain = str_replace('https://', '', $domain);
299 $domain = str_replace('www.', '', $domain);
300 $domain = rtrim($domain, '/');
301 return $domain;
302 }
303
304 public function fetchAvalexDse() {
305 $apiUrl = $this->apiUrl . '/datenschutzerklaerung';
306
307 if ($this->recursiveCalls == 1) {
308 $apiUrl = $this->fallbackApiUrl . '/datenschutzerklaerung';
309 }
310
311 $apiUrl = add_query_arg('apikey', $this->apiKey, $apiUrl);
312 $response = wp_remote_get($apiUrl);
313 $responseCode = wp_remote_retrieve_response_code($response);
314
315 if ($responseCode == 401) {
316 // API Key not authorized, shouldn't happen at this point, but you never know.
317 return;
318 }
319
320 if ($responseCode == 200) {
321 // We got something back, let's see if we the body has some data.
322 $data = wp_remote_retrieve_body($response);
323
324 // If the data is empty, we do nothing to avoid overwriting the DSE with empty content.
325 if (empty($data)) {
326 return false;
327 }
328
329 // Alright, we should be safe to actually save the dse in the database. But to be sure, we sanitize the data.
330 $sanitizedData = sanitize_post_field('post_content', trim($data), false, 'display');
331 $trimmedData = preg_replace("/\r|\n/", '', $sanitizedData);
332 $this->dseHtml = $trimmedData;
333 $this->writeDseIntoDatabase();
334 $this->recursiveCalls = 0;
335
336 // Set the data for the successfull update notice.
337 $this->notice = true;
338 $this->noticeType = 'success';
339 $this->noticeMessage = 'Der API Key und die DSE wurden aktualisiert.';
340
341 // Now we only need to delete the cache and we are done.
342 $this->emptyCache();
343
344 return true;
345 }
346
347 if (is_wp_error($response)) {
348 if ($this->recursiveCalls == 0) {
349 $this->recursiveCalls = 1;
350 return $this->fetchAvalexDse();
351 }
352
353 $errorMessage = $response->get_error_message();
354 $this->notice = true;
355 $this->noticeType = 'error';
356 $this->noticeMessage = 'Beim Datenabgleich mit dem Avalex Server ist etwas schiefgelaufen. Bitte wenden Sie sich an den Support mit den folgenden Informationen:<br>' . $errorMessage;
357 return false;
358 }
359 }
360
361 public function writeDseIntoDatabase() {
362 if (!$this->dseHtml) {
363 return;
364 }
365
366 // Write our new data into the database.
367 global $wpdb;
368 $wpdb->replace(
369 $this->tableName,
370 array(
371 'id' => 1,
372 'time' => current_time('mysql'),
373 'data' => $this->dseHtml,
374 ),
375 array(
376 '%d',
377 '%s',
378 '%s',
379 )
380 );
381 }
382
383 public function getDseFromDatabase() {
384 // Write our new data into the database.
385 global $wpdb;
386 $dseRow = $wpdb->get_results("SELECT * FROM $this->tableName");
387
388 if (!$dseRow) {
389 return;
390 }
391
392 // return $dseRow[0]->data;
393 }
394
395 public function renderAvalexShortcode() {
396 if (!$this->isKeyValid) {
397
398 // Try to revalidate.
399 if($this->validateApiKey()) {
400 $this->renderAvalexShortcode();
401 }
402
403 return 'Avalex ist noch nicht fertig eingerichtet.';
404 }
405
406 // First we check if by whatever reason the dse is empty, if it is, we try to fetch the new data.
407 if (!$this->dseHtml || empty($this->dseHtml)) {
408 if (!$this->validateApiKey()) {
409 if($this->addNotice()) {
410 return;
411 }
412 return 'API Key ungültig.';
413 }
414
415 if (!$this->validateDomain()) {
416 return 'Domain ungültig';
417 }
418
419 if (!$this->fetchAvalexDse() && $this->recursiveCalls < 2) {
420 $this->recursiveCalls++;
421 $this->renderAvalexShortcode();
422 return false;
423 }
424 }
425
426 // We have a DSE and we will use it!
427 return $this->dseHtml;
428 }
429
430 public function getDseTime() {
431 global $wpdb;
432 $result = $wpdb->get_row("SELECT time FROM $this->tableName");
433
434 if (!$result) {
435 echo 'Noch keine DSE vorhanden.';
436 }
437
438 echo $result->time;
439 }
440
441 public function pluginUpdateNotification($transient) {
442 if (empty($transient->checked)) {
443 return $transient;
444 }
445
446 $url = $this->apiUrl . '/files/wordpress/package.json';
447
448 if ($this->recursiveCalls == 1) {
449 $url = $this->fallbackApiUrl . '/files/wordpress/package.json';
450 }
451
452 $response = wp_remote_get($url);
453
454 if (is_wp_error($response)) {
455 if ($this->recursiveCalls == 0) {
456 $this->recursiveCalls = 1;
457 return $this->pluginUpdateNotification($transient);
458 }
459
460 $errorMessage = $response->get_error_message();
461 $this->notice = true;
462 $this->noticeType = 'error';
463 $this->noticeMessage = 'Beim Datenabgleich mit dem Avalex Server ist etwas schiefgelaufen. Bitte wenden Sie sich an den Support mit den folgenden Informationen:<br>' . $errorMessage;
464 return false;
465 }
466
467 $body = json_decode(wp_remote_retrieve_body($response));
468 $version = $body->version;
469
470 $pluginData = get_plugin_data(__FILE__, false, false);
471
472 if (version_compare($version, $pluginData['Version'], '<=')) {
473 return $transient;
474 }
475
476 if ($this->recursiveCalls == 0) {
477 $updateInfo = array(
478 'plugin' => plugin_basename(__FILE__),
479 'slug' => plugin_basename(__FILE__),
480 'new_version' => $version,
481 'url' => 'https://avalex.de',
482 'package' => 'https://avalex.de/files/wordpress/avalex_wordpress.zip',
483 );
484 }
485
486 if ($this->recursiveCalls == 1) {
487 $updateInfo = array(
488 'plugin' => plugin_basename(__FILE__),
489 'slug' => plugin_basename(__FILE__),
490 'new_version' => $version,
491 'url' => 'https://avalex.de',
492 'package' => 'https://proxy.avalex.de/files/wordpress/avalex_wordpress.zip',
493 );
494 }
495
496 $this->recursiveCalls = 0;
497
498 $transient->response[plugin_basename(__FILE__)] = (object) $updateInfo;
499 return $transient;
500 }
501
502 public function forceUpdate() {
503 if (!isset($_GET['force_dse_update']) || $_GET['force_dse_update'] != true) {
504 return;
505 }
506
507 $this->fetchAvalexDse();
508 $this->emptyCache();
509 }
510
511 public function emptyCache() {
512 // We want to get all folders in the cache path, not files. Atleast not in the root of the cache folder.
513 $objects = glob($this->cachePath . '/*');
514 foreach ($objects as $object) {
515 if (!is_dir($object)) {
516 continue;
517 }
518 $this->emptyFolder($object);
519 }
520 }
521
522 public function emptyFolder($dir) {
523 $objects = glob($dir . '/*');
524
525 foreach ($objects as $object) {
526 if (!is_dir($object)) {
527 $this->deleteFile($object);
528 continue;
529 }
530
531 // Now we know, this is a folder, so restart the process.
532 $this->emptyFolder($object);
533 }
534 }
535
536 public function deleteFile($file) {
537 unlink($file);
538 }
539
540 public function addSettingsLink($links) {
541 $links = array_merge(array(
542 '<a href="' . esc_url(admin_url('/options-general.php?page=avalex')) . '">Einstellungen</a>',
543 ), $links);
544 return $links;
545 }
546 }
547
548 // Call our class.
549 new Avalex();
550