PluginProbe
wpForo Forum / 3.2.1
wpForo Forum v3.2.1
3.2.1 3.2.0 3.1.7 3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 All 141 releases
← All changes | admin/pages/news/src/Services/EmailService.php +99 -0 3.1.6 → 3.2.1 View file →
@@ -375,8 +375,107 @@
375 375 }
376 376 }
377 377
378 378 /**
379 + * Updates that can't be installed — fired by the license module (in cron) when
380 + * WordPress's own update check found new versions of licensed addons while this
381 + * site blocks plugin installs (DISALLOW_FILE_MODS / read-only plugins folder), so
382 + * neither the auto-updater nor the Updates screen can install them.
383 + *
384 + * Every administrator gets ONE email listing the versions they were not told
385 + * about yet — per-admin keys "plugin_slug:version", so each release notifies once
386 + * and a later release notifies again. Own email-only category 'blocked_update'
387 + * (allows() also covers the master unsubscribe). No proxy request: templates come
388 + * from the cached daily payload or the local fallback, so consent is not involved.
389 + *
390 + * @param array $updates plugin_slug => [name, current_version, new_version]
391 + */
392 + public function handle_blocked_updates( array $updates ): void {
393 + $clean = [];
394 + foreach( $updates as $slug => $update ) {
395 + if( ! is_array( $update ) || empty( $update['new_version'] ) ) continue;
396 + $slug = sanitize_key( (string) $slug );
397 + if( $slug === '' ) continue;
398 + $new = sanitize_text_field( (string) $update['new_version'] );
399 + $clean[ $slug ] = [
400 + 'key' => $slug . ':' . $new,
401 + 'name' => sanitize_text_field( (string) ( $update['name'] ?? $slug ) ),
402 + 'current' => sanitize_text_field( (string) ( $update['current_version'] ?? '' ) ),
403 + 'new' => $new,
404 + ];
405 + }
406 + if( ! $clean ) return;
407 +
408 + $templates = $this->get_blocked_update_templates();
409 + $meta_key = $this->config->get_emailed_blocked_updates_meta();
410 +
411 + foreach( $this->get_recipients() as $user ) {
412 + if( empty( $user->user_email ) ) continue;
413 + if( ! $this->prefs->allows( $user->ID, PrefsService::CHANNEL_EMAILS, 'blocked_update' ) ) continue;
414 +
415 + $sent = get_user_meta( $user->ID, $meta_key, true );
416 + $sent = is_array( $sent ) ? $sent : [];
417 + $fresh = array_filter( $clean, fn( $u ) => ! in_array( $u['key'], $sent, true ) );
418 + if( ! $fresh ) continue;
419 +
420 + $rows = '';
421 + foreach( $fresh as $u ) {
422 + $rows .= str_replace(
423 + [ '{product_name}', '{current_version}', '{new_version}' ],
424 + [ esc_html( $u['name'] ), esc_html( $u['current'] !== '' ? $u['current'] : '—' ), esc_html( $u['new'] ) ],
425 + $templates['row']
426 + );
427 + }
428 + $count = (string) count( $fresh );
429 + $body = str_replace( [ '{update_rows}', '{update_count}' ], [ $rows, $count ], $templates['body_html'] );
430 + $subject = str_replace( '{update_count}', $count, $templates['subject'] );
431 +
432 + if( $this->send( $user, $subject, $body ) ) {
433 + $sent = array_merge( $sent, array_column( $fresh, 'key' ) );
434 + update_user_meta( $user->ID, $meta_key, array_slice( array_values( array_unique( $sent ) ), -100 ) );
435 + }
436 + }
437 + }
438 +
439 + /**
440 + * Blocked-update templates: proxy-editable versions from the cached daily news
441 + * payload (`blocked_update_email`), with a complete local fallback.
442 + */
443 + private function get_blocked_update_templates(): array {
444 + $payload = get_transient( $this->config->get_news_transient() );
445 + $remote = is_array( $payload ) && ! empty( $payload['blocked_update_email']['body_html'] ) ? $payload['blocked_update_email'] : null;
446 + $row = '<div style="border:1px solid #e2e4e8;border-left:4px solid #996800;border-radius:4px;padding:12px 18px;margin:0 0 12px;">'
447 + . '<div style="font-size:14px;color:#1d2327;"><strong>{product_name}</strong></div>'
448 + . '<div style="font-size:13px;color:#50575e;margin-top:4px;">Installed: {current_version} &rarr; new version: <strong>{new_version}</strong></div>'
449 + . '</div>';
450 +
451 + if( $remote ) {
452 + return [
453 + 'subject' => ! empty( $remote['subject'] ) ? $remote['subject'] : __( 'New addon versions can\'t be installed on {site_name}', 'gvectors' ),
454 + 'body_html' => $remote['body_html'],
455 + 'row' => ! empty( $remote['row'] ) ? $remote['row'] : $row,
456 + ];
457 + }
458 +
459 + return [
460 + 'subject' => __( 'New addon versions can\'t be installed on {site_name}', 'gvectors' ),
461 + 'body_html' => '<div style="background:#f4f5f7;padding:24px 0;font-family:Arial,Helvetica,sans-serif;">'
462 + . '<div style="max-width:600px;margin:0 auto;background:#ffffff;border-radius:8px;border:1px solid #e2e4e8;padding:28px;">'
463 + . '<p style="margin:0 0 16px;color:#1d2327;font-size:15px;">Hi {admin_name},</p>'
464 + . '<p style="margin:0 0 20px;color:#50575e;font-size:14px;line-height:1.6;">WordPress found new versions of your licensed gVectors addons on {site_name}, but it could not install them: this website does not allow WordPress to download and install plugins (for example <code>DISALLOW_FILE_MODS</code> in <code>wp-config.php</code> or a read-only plugins folder).</p>'
465 + . '{update_rows}'
466 + . '<div style="margin-top:20px;font-size:13px;line-height:1.6;color:#1d4d2b;background:#edfaef;border:1px solid #b8e6bf;border-radius:4px;padding:12px 16px;">'
467 + . '<strong>Recommended &mdash; install the updates immediately:</strong> restore the default WordPress permissions: remove <code>DISALLOW_FILE_MODS</code> from <code>wp-config.php</code> (or set it to <code>false</code>) and make sure WordPress can write to <code>wp-content/plugins/</code> &mdash; your hosting provider can help with that. '
468 + . 'The updates then appear in Dashboard &rarr; Updates and install with one click, and future releases arrive the same natural way.'
469 + . '<div style="margin-top:6px;font-size:12px;">Tip: if you only want to block code editing in the dashboard, <code>DISALLOW_FILE_EDIT</code> does that without blocking updates.</div>'
470 + . '</div>'
471 + . '<p style="margin:16px 0 0;color:#8c8f94;font-size:12px;line-height:1.6;">If you can\'t change these settings, contact gVectors support to receive a manual download link for the new version(s).</p>'
472 + . '</div></div>',
473 + 'row' => $row,
474 + ];
475 + }
476 +
477 + /**
379 478 * Purchase templates: proxy-editable versions from the cached daily news
380 479 * payload, with a complete local fallback (a purchase must never lose its
381 480 * confirmation email because the cache is cold).
382 481 */