PluginProbe
wpForo Forum / 3.2.0
wpForo Forum v3.2.0
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/license/src/Services/AddonsService.php +84 -26 3.1.7 → 3.2.0 View file →
@@ -29,8 +29,9 @@
29 29 * only offers updates for licenses that are active/trial AND activated for this domain.
30 30 * Expired licenses are NOT offered updates (addon keeps working but no new versions).
31 31 */
32 32 private $update_check_done = false;
33 + private $pruned = false;
33 34 private $all_addons_transient_name;
34 35 private $signature_check_hook;
35 36 private $license_check_hook;
36 37 private $tampered_option;
@@ -1359,8 +1360,10 @@
1359 1360 * Checks every installed plugin that matches a known addon slug from the proxy.
1360 1361 * Uses a grace period: show FATAL notice first, deactivate after TAMPER_GRACE_DAYS.
1361 1362 */
1362 1363 public function verify_all_addon_signatures(): void {
1364 + $this->prune_missing_addons();
1365 +
1363 1366 // Skip all checks on development/local/staging environments
1364 1367 if( LicenseModule::is_development_site() ) return;
1365 1368
1366 1369 // Get the full list of known addon slugs from the proxy server
@@ -1518,12 +1521,14 @@
1518 1521 * Records the first time admin sees each tamper notice.
1519 1522 */
1520 1523 public function track_tamper_notice_view(): void {
1521 1524 if( ! current_user_can( 'administrator' ) ) return;
1522 -
1525 +
1526 + $this->prune_missing_addons();
1527 +
1523 1528 $tampered = get_option( $this->tampered_option, [] );
1524 1529 if( empty( $tampered ) ) return;
1525 -
1530 +
1526 1531 $seen = get_option( $this->tamper_dismissed_option, [] );
1527 1532 $updated = false;
1528 1533
1529 1534 foreach( $tampered as $slug => $info ) {
@@ -1562,19 +1567,70 @@
1562 1567 set_transient( $flag, 1, 12 * HOUR_IN_SECONDS );
1563 1568 }
1564 1569
1565 1570 /**
1566 - * When a plugin is deleted, clear its tamper flag if it had one.
1571 + * When a plugin is deleted, forget all stored notice/tamper/legacy data for it.
1567 1572 */
1568 1573 public function on_plugin_deleted( string $plugin_file, bool $deleted ): void {
1569 1574 if( ! $deleted ) return;
1570 -
1575 +
1571 1576 $slug = dirname( $plugin_file );
1572 1577 if( $slug && $slug !== '.' ) {
1573 - $this->clear_tamper_flag( $slug );
1574 - $this->clear_legacy_cache( $slug );
1578 + $this->forget_addon( $slug );
1575 1579 }
1576 1580 }
1581 +
1582 + /**
1583 + * Check if an addon physically exists on disk as a real plugin.
1584 + * An empty leftover folder (no plugin header file) counts as not present.
1585 + */
1586 + private function is_addon_present( string $plugin_slug ): bool {
1587 + if( empty( $plugin_slug ) || ! is_dir( WP_PLUGIN_DIR . '/' . $plugin_slug ) ) return false;
1588 +
1589 + return ! empty( $this->get_installed_plugin_file( $plugin_slug ) );
1590 + }
1591 +
1592 + /**
1593 + * Remove all per-addon notice, tamper and legacy-cache data for a slug.
1594 + * License records are intentionally kept — they are paid entitlements used by the store page.
1595 + */
1596 + private function forget_addon( string $plugin_slug ): void {
1597 + $expired = get_option( $this->expired_notice_option, [] );
1598 + if( isset( $expired[ $plugin_slug ] ) ) {
1599 + unset( $expired[ $plugin_slug ] );
1600 + update_option( $this->expired_notice_option, $expired );
1601 + }
1602 +
1603 + $this->clear_tamper_flag( $plugin_slug );
1604 + $this->clear_legacy_cache( $plugin_slug );
1605 +
1606 + foreach( [ 'tampered', 'expired', 'legacy' ] as $type ) {
1607 + delete_transient( 'gvectors_' . $type . '_dismissed_' . $plugin_slug );
1608 + }
1609 + }
1610 +
1611 + /**
1612 + * Rewind stored per-addon data for addons that no longer physically exist
1613 + * (e.g. deleted via FTP / file manager, bypassing the deleted_plugin hook).
1614 + * Runs once per request per instance.
1615 + */
1616 + public function prune_missing_addons(): void {
1617 + if( $this->pruned ) return;
1618 + $this->pruned = true;
1619 +
1620 + $slugs = [];
1621 + foreach( [ $this->expired_notice_option, $this->tampered_option, $this->tamper_dismissed_option, $this->legacy_licenses_option, $this->legacy_notice_option ] as $option ) {
1622 + $data = get_option( $option, [] );
1623 + if( is_array( $data ) ) $slugs = array_merge( $slugs, array_keys( $data ) );
1624 + }
1625 +
1626 + foreach( array_unique( $slugs ) as $slug ) {
1627 + $slug = (string) $slug;
1628 + if( ! $this->is_addon_present( $slug ) ) {
1629 + $this->forget_addon( $slug );
1630 + }
1631 + }
1632 + }
1577 1633
1578 1634 /**
1579 1635 * Clear the legacy license cache for a specific addon.
1580 1636 */
@@ -1600,17 +1656,22 @@
1600 1656 * Marks expired licenses for admin notice display.
1601 1657 * Does NOT deactivate addons for expired licenses - they keep working.
1602 1658 */
1603 1659 public function check_all_license_validity(): void {
1660 + $this->prune_missing_addons();
1661 +
1604 1662 $licenses = $this->licenseService->get_all();
1605 1663 if( empty( $licenses ) ) return;
1606 -
1664 +
1607 1665 $expired_notices = get_option( $this->expired_notice_option, [] );
1608 -
1666 +
1609 1667 foreach( $licenses as $license ) {
1610 1668 $plugin_slug = $license['plugin_slug'] ?? '';
1611 1669 if( empty( $plugin_slug ) ) continue;
1612 - if( ! $this->is_installed( $plugin_slug ) ) continue;
1670 + if( ! $this->is_installed( $plugin_slug ) ) {
1671 + unset( $expired_notices[ $plugin_slug ] );
1672 + continue;
1673 + }
1613 1674
1614 1675 $status = $license['status'] ?? '';
1615 1676 $expires_at = $license['expires_at'] ?? '';
1616 1677 $is_expired = false;
@@ -1840,26 +1901,18 @@
1840 1901 */
1841 1902 public function tampered_addon_notice(): void {
1842 1903 if( ! $this->is_notice_page() ) return;
1843 1904 if( ! current_user_can( 'administrator' ) ) return;
1905 +
1906 + $this->prune_missing_addons();
1907 +
1844 1908 if( LicenseModule::is_development_site() ) return;
1845 -
1909 +
1846 1910 $tampered = get_option( $this->tampered_option, [] );
1847 1911 if( empty( $tampered ) ) return;
1848 -
1849 - $changed = false;
1912 +
1850 1913 foreach( $tampered as $slug => $info ) {
1851 - if( ! is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) {
1852 - unset( $tampered[ $slug ] );
1853 - $changed = true;
1854 - }
1855 - }
1856 - if( $changed ) {
1857 - update_option( $this->tampered_option, $tampered );
1858 - }
1859 - if( empty( $tampered ) ) return;
1860 -
1861 - foreach( $tampered as $slug => $info ) {
1914 + if( ! $this->is_addon_present( $slug ) ) continue;
1862 1915 if( get_transient( 'gvectors_tampered_dismissed_' . $slug ) ) continue;
1863 1916
1864 1917 $files = $info['files'] ?? [];
1865 1918 $reason = $info['reason'] ?? 'tampered';
@@ -2018,12 +2071,15 @@
2018 2071 public function expired_license_notice(): void {
2019 2072 if( ! $this->is_notice_page() ) return;
2020 2073 if( ! current_user_can( 'administrator' ) ) return;
2021 2074
2075 + $this->prune_missing_addons();
2076 +
2022 2077 $expired = get_option( $this->expired_notice_option, [] );
2023 2078 if( empty( $expired ) ) return;
2024 -
2079 +
2025 2080 foreach( $expired as $slug => $info ) {
2081 + if( ! $this->is_addon_present( $slug ) ) continue;
2026 2082 if( get_transient( 'gvectors_expired_dismissed_' . $slug ) ) continue;
2027 2083
2028 2084 $product_name = $info['product_name'] ?? $slug;
2029 2085 $has_update = ! empty( $info['has_update'] );
@@ -2074,11 +2130,13 @@
2074 2130 public function legacy_license_notice(): void {
2075 2131 if( ! $this->is_notice_page() ) return;
2076 2132 if( ! current_user_can( 'administrator' ) ) return;
2077 2133
2134 + $this->prune_missing_addons();
2135 +
2078 2136 $notices = get_option( $this->legacy_notice_option, [] );
2079 2137 if( empty( $notices ) ) return;
2080 -
2138 +
2081 2139 $addons_page_url = admin_url( $this->config->get_dashboard_addons_store_url() );
2082 2140
2083 2141 foreach( $notices as $slug => $info ) {
2084 2142 // Only show notices for expired legacy licenses
@@ -2084,9 +2142,9 @@
2084 2142 // Only show notices for expired legacy licenses
2085 2143 if( empty( $info['status'] ) || $info['status'] !== 'expired' ) continue;
2086 2144
2087 2145 // Verify the addon is still installed
2088 - if( ! is_dir( WP_PLUGIN_DIR . '/' . $slug ) ) continue;
2146 + if( ! $this->is_addon_present( $slug ) ) continue;
2089 2147
2090 2148 if( get_transient( 'gvectors_legacy_dismissed_' . $slug ) ) continue;
2091 2149
2092 2150 $plugin_name = $info['plugin_name'] ?? $slug;