PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / pro-plugin-provisioning / module.php

module.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/pro-plugin-provisioning/module.php

137 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Pro-plugin-provisioning module.
4 *
5 * Lets Templately install two paid plugins on a user's behalf during import —
6 * Essential Addons Pro (Elementor) and Essential Blocks Pro (Gutenberg) — when the
7 * user's Templately subscription entitles them to it.
8 *
9 * The module owns NO REST route and NO import logic. It answers one filter,
10 * {@see self::ARCHIVE_FILTER}, with the path to a downloaded plugin archive, and
11 * `Utils\Installer` does the rest. That is what lets a single change serve both the
12 * full-site (pack) import and the single-item import, and what makes "behaves exactly
13 * as before when this module is disabled" (spec 059 FR-025) free rather than a branch:
14 * a disabled module never adds the filter, so `Installer` falls back to the `pro_plugin`
15 * refusal it has always returned.
16 *
17 * Entitlement is decided REMOTELY. Nothing here reads a plan, a tier, or a subscription
18 * value (FR-003) — the download service is the only authority, and its answer is never
19 * cached (FR-019), so an upgraded plan takes effect on the next attempt.
20 *
21 * @package Templately
22 */
23
24 namespace Templately\Modules\ProPluginProvisioning;
25
26 use Templately\Core\Module_Base;
27 use Templately\Utils\Options;
28
29 class Module extends Module_Base {
30
31 /**
32 * Ask any registered source for an installable archive.
33 *
34 * `apply_filters( self::ARCHIVE_FILTER, null, array $request ): string|null`
35 *
36 * See specs/059-pro-plugin-provisioning/contracts/provisioning-source.md for the
37 * full contract. In short: return an absolute path to a readable file or the value
38 * you were given; never a URL, never a WP_Error, never an exception, never output.
39 */
40 const ARCHIVE_FILTER = 'templately_pro_plugin_archive';
41
42 /**
43 * Seconds a source may spend acquiring an archive, filterable per site.
44 *
45 * Deliberately far longer than an ordinary cloud call: this is a real plugin
46 * download of several megabytes, not an API response.
47 */
48 const TIMEOUT_FILTER = 'templately_pro_plugin_provisioning_timeout';
49
50 /**
51 * 90, not the 120 first chosen. The full-site import client runs an SSE stall
52 * watchdog of exactly 120s (`useFullSiteImport.ts`), cleared only by a real frame —
53 * and no frame is emitted while `wp_remote_get()` blocks on the download. A budget
54 * equal to the watchdog let a slow host trip it: the client reconnected, took over
55 * the run lock, and re-ran the dependency step, racing a second download of the same
56 * archive against the first. 30s of headroom keeps the download inside one frame gap.
57 * Sites with genuinely slow links raise it through the filter — knowingly.
58 */
59 const DEFAULT_TIMEOUT = 90;
60
61 /**
62 * Reserved development credentials the dev-only mock source recognises.
63 *
64 * They live HERE, on the shipped module, rather than in the mock, so the mock, the
65 * tests and the E2E helper all read one declaration and cannot drift. They are inert
66 * in production: nothing in a production build reads them, because the only reader —
67 * `modules/developer/pro-provisioning-mock/` — is excluded from the zip by
68 * `.distignore`.
69 *
70 * Neither value authenticates against the Templately cloud. They are written into the
71 * site's stored `api_key` for the duration of a test or a manual run, then restored.
72 */
73 const MOCK_CREDENTIAL_GRANTED = 'templately-mock-pro-granted';
74 const MOCK_CREDENTIAL_DENIED = 'templately-mock-pro-denied';
75
76 public function get_name(): string {
77 return 'pro-plugin-provisioning';
78 }
79
80 protected function init_hooks(): void {
81 add_filter( self::ARCHIVE_FILTER, [ $this, 'provide_archive' ], 10, 2 );
82 }
83
84 /**
85 * The live WPDeveloper source.
86 *
87 * Registered at priority 10 so the dev mock (priority 5) can answer first for its two
88 * reserved credentials and fall through for every other one.
89 *
90 * @param string|null $archive_path Path supplied by an earlier source, or null.
91 * @param array $request plugin_file, slug, platform, credential.
92 * @return string|null
93 */
94 public function provide_archive( $archive_path, $request ) {
95 if ( is_string( $archive_path ) && '' !== $archive_path ) {
96 // An earlier source already answered. Do not spend a request re-answering.
97 return $archive_path;
98 }
99
100 return ProvisioningSource::get_instance()->fetch( is_array( $request ) ? $request : [] );
101 }
102
103 /**
104 * The site's stored Templately credential, or an empty string when not connected.
105 *
106 * Read through one accessor so the "no credential ⇒ no attempt" rule (FR-015) has a
107 * single definition, and so the value has exactly one place it can be logged from —
108 * which is nowhere.
109 */
110 public static function credential(): string {
111 $api_key = Options::get_instance()->get( 'api_key' );
112
113 return is_string( $api_key ) ? trim( $api_key ) : '';
114 }
115
116 /**
117 * Whether anything can answer the archive filter right now.
118 *
119 * The honest "is this feature on" test. A disabled module never ran `init_hooks()`,
120 * so its listener is absent; the dev mock declares a dependency on this module, so it
121 * is skipped alongside. `class_exists()` cannot tell you this — the autoloader
122 * namespace is registered for inactive modules too.
123 */
124 public static function is_live(): bool {
125 return false !== has_filter( self::ARCHIVE_FILTER );
126 }
127
128 /**
129 * Seconds an acquisition attempt may take.
130 */
131 public static function timeout(): int {
132 $timeout = (int) apply_filters( self::TIMEOUT_FILTER, self::DEFAULT_TIMEOUT );
133
134 return $timeout > 0 ? $timeout : self::DEFAULT_TIMEOUT;
135 }
136 }
137