PluginProbe
avalex – Automatisch sichere Rechtstexte / 1.5.8
avalex – Automatisch sichere Rechtstexte v1.5.8
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.8, at avalex.php

584 lines 18.3 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.8
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 return;
156 }
157
158 if (!$this->saveApiKey()) {
159 return;
160 }
161
162 if (!$this->validateApiKey()) {
163 return;
164 }
165
166 if (!$this->validateDomain()) {
167 return;
168 }
169
170 $this->fetchAvalexDse();
171 }
172
173 public function addAdminMenu() {
174 add_options_page('avalex', 'avalex', 'manage_options', 'avalex', array($this, 'avalexAdminPage'));
175 }
176
177 public function avalexAdminPage() {
178 include $this->pluginPath . '/templates/admin_page.php';
179 }
180
181 public function addNotice() {
182 if (!$this->notice) {
183 return false;
184 }
185
186 echo '<div class="notice notice-' . $this->noticeType . '"><p>' . $this->noticeMessage . '</p></div>';
187 return true;
188 }
189
190 private function saveApiKey() {
191 // Check if api key was entered.
192 $apiKey = sanitize_text_field($_POST['avalex_api_key']);
193 update_option('avalex_api_key', $apiKey);
194
195 if (!$apiKey) {
196 $this->notice = true;
197 $this->noticeType = 'error';
198 $this->noticeMessage = __('Please enter an API key.', 'Avalex');
199 return false;
200 }
201
202 if ($apiKey) {
203 $this->apiKey = $apiKey;
204 return true;
205 }
206 }
207
208 private function showApiKey() {
209 if ($this->apiKey) {
210 echo $this->apiKey;
211 return;
212 }
213
214 return false;
215 }
216
217 private function validateApiKey() {
218
219 // Build the api url.
220 $apiUrlDomain = $this->apiUrl . '/api_keys/is_configured.json';
221 if ($this->recursiveCalls == 1) {
222 $apiUrlDomain = $this->fallbackApiUrl . '/api_keys/is_configured.json';
223 }
224
225 $apiUrlDomain = add_query_arg('apikey', $this->apiKey, $apiUrlDomain);
226
227 // Call the api.
228 $response = wp_remote_get($apiUrlDomain, array('timeout' => 1));
229 $responseCode = wp_remote_retrieve_response_code($response);
230
231 // If we have a 401, the key is not valid.
232 if ($responseCode == 401) {
233 $this->notice = true;
234 $this->noticeType = 'error';
235 $this->noticeMessage = 'Der API-Key ist ungültig.';
236 $this->isKeyValid = false;
237 update_option('avalex_valid_api_key', false);
238 return false;
239 }
240
241 // 200 means api key is valid, so we can grab the domain and proceed.
242 if ($responseCode == 200) {
243 update_option('avalex_valid_api_key', true);
244 $this->isKeyValid = 1;
245 $this->response = json_decode(wp_remote_retrieve_body($response), true);
246 $this->recursiveCalls = 0;
247 return true;
248 }
249
250 if (is_wp_error($response)) {
251 // When this is the first time the error happens, we want to try the fallback url.
252 if ($this->recursiveCalls == 0) {
253 $this->recursiveCalls = 1;
254 return $this->validateApiKey();
255 }
256
257 $errorMessage = $response->get_error_message();
258 $this->notice = true;
259 $this->noticeType = 'error';
260 $this->noticeMessage = 'Beim Datenabgleich mit dem Avalex Server ist etwas schiefgelaufen. Bitte wenden Sie sich an den Support mit den folgenden Informationen:<br>' . $errorMessage;
261 return false;
262 }
263
264 if ($responseCode == 400) {
265 // Website not configured.
266 $this->notice = true;
267 $this->noticeType = 'error';
268 $this->noticeMessage = 'Webseite noch nicht fertig konfiguriert. Bitte loggen Sie sich bei Avalex ein und schließen die Konfiguration ab.';
269 $this->isKeyValid = 1;
270 update_option('avalex_valid_api_key', true);
271 return false;
272 }
273
274
275 update_option('avalex_valid_api_key', false);
276 return false;
277 }
278
279 private function validateDomain() {
280 $wordPressUrl = $this->trimDomain(home_url());
281 $avalexDomain = $this->trimDomain($this->response['domain']);
282
283 if ($wordPressUrl != $avalexDomain) {
284 $this->isKeyValid = false;
285 $this->notice = true;
286 $this->noticeType = 'error';
287 $this->noticeMessage = 'Die aktuelle Domain des Servers (' . home_url() . ') stimmt nicht mit der Domain überein, die Sie in avalex eingegeben haben.';
288 return false;
289 }
290
291 // Domain is valid, so set the internal state.
292 $this->isDomainValid = true;
293 return true;
294 }
295
296 public function trimDomain($domain) {
297 // Remove protocoll and www.
298 $domain = str_replace('http://', '', $domain);
299 $domain = str_replace('https://', '', $domain);
300 $domain = str_replace('www.', '', $domain);
301 $domain = rtrim($domain, '/');
302 return $domain;
303 }
304
305 public function fetchAvalexDse() {
306 $apiUrl = $this->apiUrl . '/datenschutzerklaerung';
307
308 if ($this->recursiveCalls == 1) {
309 $apiUrl = $this->fallbackApiUrl . '/datenschutzerklaerung';
310 }
311
312 $apiUrl = add_query_arg('apikey', $this->apiKey, $apiUrl);
313 $response = wp_remote_get($apiUrl);
314 $responseCode = wp_remote_retrieve_response_code($response);
315
316 if ($responseCode == 401) {
317 // API Key not authorized, shouldn't happen at this point, but you never know.
318 return;
319 }
320
321 if ($responseCode == 200) {
322 // We got something back, let's see if the body has some data.
323 $data = wp_remote_retrieve_body($response);
324
325 // If the data is empty, we do nothing to avoid overwriting the DSE with empty content.
326 if (empty($data)) {
327 return false;
328 }
329
330 // Alright, we should be safe to actually save the dse in the database. But to be sure, we sanitize the data.
331 $sanitizedData = sanitize_post_field('post_content', trim($data), false, 'display');
332 $trimmedData = preg_replace("/\r|\n/", '', $sanitizedData);
333 $this->dseHtml = $trimmedData;
334 $this->writeDseIntoDatabase();
335 $this->recursiveCalls = 0;
336
337 // Set the data for the successfull update notice.
338 $this->notice = true;
339 $this->noticeType = 'success';
340 $this->noticeMessage = 'Der API Key und die DSE wurden aktualisiert.';
341
342 // Now we delete the whole cache of WordPress to make sure the update actually shows.
343 $this->emptyCache();
344
345 return true;
346 }
347
348 if (is_wp_error($response)) {
349 if ($this->recursiveCalls == 0) {
350 $this->recursiveCalls = 1;
351 return $this->fetchAvalexDse();
352 }
353
354 $errorMessage = $response->get_error_message();
355 $this->notice = true;
356 $this->noticeType = 'error';
357 $this->noticeMessage = 'Beim Datenabgleich mit dem Avalex Server ist etwas schiefgelaufen. Bitte wenden Sie sich an den Support mit den folgenden Informationen:<br>' . $errorMessage;
358 return false;
359 }
360 }
361
362 public function writeDseIntoDatabase() {
363 if (!$this->dseHtml) {
364 return;
365 }
366
367 // Write our new data into the database.
368 global $wpdb;
369 $wpdb->replace(
370 $this->tableName,
371 array(
372 'id' => 1,
373 'time' => current_time('mysql'),
374 'data' => $this->dseHtml,
375 ),
376 array(
377 '%d',
378 '%s',
379 '%s',
380 )
381 );
382 }
383
384 public function getDseFromDatabase() {
385 // Write our new data into the database.
386 global $wpdb;
387 $dseRow = $wpdb->get_results("SELECT * FROM $this->tableName");
388
389 if (!$dseRow) {
390 return;
391 }
392
393 return $dseRow[0]->data;
394 }
395
396 public function renderAvalexShortcode() {
397 if (!$this->isKeyValid) {
398
399 // Try to revalidate.
400 if($this->validateApiKey()) {
401 $this->renderAvalexShortcode();
402 }
403
404 return 'Avalex ist noch nicht fertig eingerichtet.';
405 }
406
407 // First we check if by whatever reason the dse is empty, if it is, we try to fetch the new data.
408 if (!$this->dseHtml || empty($this->dseHtml)) {
409 if (!$this->validateApiKey()) {
410 if($this->addNotice()) {
411 return;
412 }
413 return 'API Key ungültig.';
414 }
415
416 if (!$this->validateDomain()) {
417 return 'Domain ungültig';
418 }
419
420 if (!$this->fetchAvalexDse() && $this->recursiveCalls < 2) {
421 $this->recursiveCalls++;
422 $this->renderAvalexShortcode();
423 return false;
424 }
425 }
426
427 // We have a DSE and we will use it!
428 return $this->dseHtml;
429 }
430
431 public function getDseTime() {
432 global $wpdb;
433 $result = $wpdb->get_row("SELECT time FROM $this->tableName");
434
435 if (!$result) {
436 echo 'Noch keine DSE vorhanden.';
437 }
438
439 echo $result->time;
440 }
441
442 public function pluginUpdateNotification($transient) {
443 if (empty($transient->checked)) {
444 return $transient;
445 }
446
447 $url = $this->apiUrl . '/files/wordpress/package.json';
448
449 if ($this->recursiveCalls == 1) {
450 $url = $this->fallbackApiUrl . '/files/wordpress/package.json';
451 }
452
453 $response = wp_remote_get($url);
454
455 if (is_wp_error($response)) {
456 if ($this->recursiveCalls == 0) {
457 $this->recursiveCalls = 1;
458 return $this->pluginUpdateNotification($transient);
459 }
460
461 $errorMessage = $response->get_error_message();
462 $this->notice = true;
463 $this->noticeType = 'error';
464 $this->noticeMessage = 'Beim Datenabgleich mit dem Avalex Server ist etwas schiefgelaufen. Bitte wenden Sie sich an den Support mit den folgenden Informationen:<br>' . $errorMessage;
465 return false;
466 }
467
468 $body = json_decode(wp_remote_retrieve_body($response));
469 $version = $body->version;
470
471 $pluginData = get_plugin_data(__FILE__, false, false);
472
473 if (version_compare($version, $pluginData['Version'], '<=')) {
474 return $transient;
475 }
476
477 if ($this->recursiveCalls == 0) {
478 $updateInfo = array(
479 'plugin' => plugin_basename(__FILE__),
480 'slug' => plugin_basename(__FILE__),
481 'new_version' => $version,
482 'url' => 'https://avalex.de',
483 'package' => 'https://avalex.de/files/wordpress/avalex_wordpress.zip',
484 );
485 }
486
487 if ($this->recursiveCalls == 1) {
488 $updateInfo = array(
489 'plugin' => plugin_basename(__FILE__),
490 'slug' => plugin_basename(__FILE__),
491 'new_version' => $version,
492 'url' => 'https://avalex.de',
493 'package' => 'https://proxy.avalex.de/files/wordpress/avalex_wordpress.zip',
494 );
495 }
496
497 $this->recursiveCalls = 0;
498
499 $transient->response[plugin_basename(__FILE__)] = (object) $updateInfo;
500 return $transient;
501 }
502
503 public function forceUpdate() {
504 if (!isset($_GET['force_dse_update']) || $_GET['force_dse_update'] != true) {
505 return;
506 }
507
508 $this->fetchAvalexDse();
509 $this->emptyCache();
510 }
511
512 public function emptyCache() {
513 // We want to get all folders in the cache path, not files. Atleast not in the root of the cache folder.
514 $objects = glob($this->cachePath . '/*');
515 foreach ($objects as $object) {
516 if (!is_dir($object)) {
517 continue;
518 }
519
520
521 $foldersToExclude = [
522 'borlabs-cookie',
523 'autoptimize',
524 'tmp',
525 ];
526 // We also want to exclude some cache paths.
527
528 $skip = false;
529 foreach($foldersToExclude as $folderName) {
530 if(strpos($object, $folderName) !== false) {
531 $skip = true;
532 break;
533 }
534 }
535
536 if($skip) {
537 continue;
538 }
539
540 $this->emptyFolder($object);
541 }
542
543 }
544
545 public function emptyFolder($dir) {
546 $objects = glob($dir . '/*');
547
548 foreach ($objects as $object) {
549 if (!is_dir($object)) {
550 $this->deleteFile($object);
551 continue;
552 }
553
554 // Now we know, this is a folder, so restart the process.
555 $this->emptyFolder($object);
556 }
557 }
558
559 public function deleteFile($file) {
560
561 // We only want to delete html or gzip files, no css or js.
562 $extension = pathinfo($file)['extension'];
563 $allowedExtensionsToDelete = [
564 'html', 'html_gzip', 'gz',
565 ];
566
567 if(!in_array($extension, $allowedExtensionsToDelete)) {
568 return;
569 }
570
571 unlink($file);
572 }
573
574 public function addSettingsLink($links) {
575 $links = array_merge(array(
576 '<a href="' . esc_url(admin_url('/options-general.php?page=avalex')) . '">Einstellungen</a>',
577 ), $links);
578 return $links;
579 }
580 }
581
582 // Call our class.
583 new Avalex();
584