PluginProbe
avalex – Automatisch sichere Rechtstexte / 2.0.6
avalex – Automatisch sichere Rechtstexte v2.0.6
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 2.0.6, at avalex.php

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