PluginProbe
Ebook Store / 6.25
Ebook Store v6.25
6.25 6.24 6.23 6.22 6.21 6.20 trunk
ebook-store / includes / ebook-store-bootstrap.php

ebook-store-bootstrap.php in Ebook Store 6.25, at includes/ebook-store-bootstrap.php

277 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ebook Store - bootstrap helpers, environment guards and upgrade routines.
4 *
5 * This file is loaded before anything else and must stay parseable on PHP 5.6,
6 * so that a site on an ancient PHP version sees a helpful admin notice instead
7 * of a white screen.
8 *
9 * @package EbookStore
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Minimum supported environment.
18 */
19 /**
20 * Absolute path to the main plugin file. Used anywhere a path must be derived
21 * without depending on __FILE__ of the including file.
22 */
23 if ( ! defined( 'EBOOK_STORE_PLUGIN_FILE' ) ) {
24 define( 'EBOOK_STORE_PLUGIN_FILE', dirname( __DIR__ ) . '/ebook_store.php' );
25 }
26
27 define( 'EBOOK_STORE_MIN_PHP', '7.4' );
28 define( 'EBOOK_STORE_MIN_WP', '5.8' );
29
30 /**
31 * The plugin version, read once from the plugin header.
32 *
33 * @return string
34 */
35 function ebook_store_get_plugin_version() {
36 static $version = null;
37
38 if ( $version !== null ) {
39 return $version;
40 }
41
42 $version = '0.0.0';
43 $file = dirname( __DIR__ ) . '/ebook_store.php';
44
45 if ( is_readable( $file ) ) {
46 if ( ! function_exists( 'get_file_data' ) ) {
47 require_once ABSPATH . 'wp-includes/functions.php';
48 }
49 $data = get_file_data( $file, array( 'Version' => 'Version' ), 'plugin' );
50 if ( ! empty( $data['Version'] ) ) {
51 $version = $data['Version'];
52 }
53 }
54
55 return $version;
56 }
57
58 /**
59 * Absolute path to the plugin directory, with a trailing slash.
60 *
61 * @return string
62 */
63 function ebook_store_path( $relative = '' ) {
64 return dirname( __DIR__ ) . '/' . ltrim( $relative, '/' );
65 }
66
67 /**
68 * URL to a plugin asset.
69 *
70 * @param string $relative Path relative to the plugin root.
71 * @return string
72 */
73 function ebook_store_url( $relative = '' ) {
74 return plugins_url( ltrim( $relative, '/' ), dirname( __DIR__ ) . '/ebook_store.php' );
75 }
76
77 /**
78 * Does the current environment meet the plugin requirements?
79 *
80 * @return true|string True, or a translated error message.
81 */
82 function ebook_store_check_environment() {
83 global $wp_version;
84
85 if ( version_compare( PHP_VERSION, EBOOK_STORE_MIN_PHP, '<' ) ) {
86 return sprintf(
87 /* translators: 1: required PHP version, 2: current PHP version. */
88 __( 'Ebook Store needs PHP %1$s or newer. This site runs PHP %2$s. Ask your host to switch the site to a supported PHP version.', 'ebook-store' ),
89 EBOOK_STORE_MIN_PHP,
90 PHP_VERSION
91 );
92 }
93
94 if ( isset( $wp_version ) && version_compare( $wp_version, EBOOK_STORE_MIN_WP, '<' ) ) {
95 return sprintf(
96 /* translators: 1: required WordPress version, 2: current WordPress version. */
97 __( 'Ebook Store needs WordPress %1$s or newer. This site runs WordPress %2$s.', 'ebook-store' ),
98 EBOOK_STORE_MIN_WP,
99 $wp_version
100 );
101 }
102
103 return true;
104 }
105
106 /* -------------------------------------------------------------------------
107 * Upgrade routines
108 * ---------------------------------------------------------------------- */
109
110 /**
111 * Run one-time migrations when the stored version is older than the file version.
112 */
113 function ebook_store_maybe_upgrade() {
114 $installed = (string) get_option( 'ebook_store_version', '' );
115 $current = ebook_store_get_plugin_version();
116
117 if ( $installed === $current ) {
118 return;
119 }
120
121 // 6.00 removed the Square integration, which required PHP 8.1 and locked
122 // out every site still on PHP 7.4.
123 if ( $installed === '' || version_compare( $installed, '6.00', '<' ) ) {
124 ebook_store_upgrade_remove_square();
125 }
126
127 // 6.23 began storing the PayPal verification toggle as an explicit '1'/'0'.
128 // A site upgrading from an earlier version can hold '', which the IPN reads
129 // as "the owner never chose, so verify" — correct, but it left the setting
130 // looking unset until the owner re-saved the PayPal tab. Write the value the
131 // site has been running with so the stored setting matches its behaviour.
132 if ( $installed !== '' && version_compare( $installed, '6.24', '<' ) ) {
133 $paypal_verify = get_option( 'paypal_verify_transactions', null );
134 if ( $paypal_verify !== null && is_scalar( $paypal_verify ) && (string) $paypal_verify === '' ) {
135 update_option( 'paypal_verify_transactions', '1' );
136 }
137 }
138
139 // Protection for the upload directories is (re)asserted on every upgrade,
140 // so a site that lost its .htaccess gets it back.
141 if ( function_exists( 'ebook_store_ensure_protected_dirs' ) ) {
142 ebook_store_ensure_protected_dirs();
143 }
144
145 update_option( 'ebook_store_version', $current, false );
146 }
147 add_action( 'admin_init', 'ebook_store_maybe_upgrade' );
148
149 /**
150 * Delete every option, transient and log file left behind by the removed
151 * Square integration, so nothing lingers in the database.
152 */
153 function ebook_store_upgrade_remove_square() {
154 global $wpdb;
155
156 $options = array(
157 'square_access_token',
158 'square_location_id',
159 'square_environment',
160 'square_webhook_signature_key',
161 'square_cancel_url',
162 'square_currency',
163 'square_currency_conversion_enabled',
164 'square_integration_enabled',
165 'square_allow_tipping',
166 'square_ask_shipping_address',
167 'square_merchant_support_email',
168 'square_button_text',
169 );
170
171 foreach ( $options as $option ) {
172 delete_option( $option );
173 }
174
175 // Checkout-state transients and per-payment creation locks.
176 $wpdb->query(
177 $wpdb->prepare(
178 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s",
179 $wpdb->esc_like( '_transient_ebook_store_square_' ) . '%',
180 $wpdb->esc_like( '_transient_timeout_ebook_store_square_' ) . '%',
181 $wpdb->esc_like( 'ebook_store_square_lock_' ) . '%'
182 )
183 );
184
185 // The old integration wrote an uncapped log file inside the plugin folder,
186 // which routinely grew past 100 MB.
187 $log = ebook_store_path( 'square-webhook.log.txt' );
188 if ( file_exists( $log ) ) {
189 @unlink( $log );
190 }
191 }
192
193 /* -------------------------------------------------------------------------
194 * Logging
195 * ---------------------------------------------------------------------- */
196
197 /**
198 * Maximum size a plugin log file may reach before it is rotated, in bytes.
199 */
200 define( 'EBOOK_STORE_LOG_MAX_BYTES', 2 * MB_IN_BYTES );
201
202 /**
203 * Append a line to a plugin log file, with rotation and a hard size cap.
204 *
205 * Logging is off unless WP_DEBUG is on or the 'ebook_store_enable_logging'
206 * filter returns true, so a production store never accumulates gigabytes of
207 * webhook chatter inside wp-content/plugins.
208 *
209 * @param string $channel Log channel, e.g. 'stripe'.
210 * @param string $message Message.
211 * @param array $context Optional context.
212 */
213 function ebook_store_log( $channel, $message, $context = array() ) {
214 $enabled = ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || defined( 'EBOOK_STORE_DEBUG' );
215
216 if ( ! apply_filters( 'ebook_store_enable_logging', $enabled, $channel ) ) {
217 return;
218 }
219
220 $dir = ebook_store_get_log_dir();
221 if ( $dir === '' ) {
222 return;
223 }
224
225 $file = $dir . '/' . sanitize_file_name( $channel ) . '.log';
226
227 if ( file_exists( $file ) && filesize( $file ) > EBOOK_STORE_LOG_MAX_BYTES ) {
228 @rename( $file, $file . '.1' );
229 }
230
231 $entry = gmdate( '[Y-m-d H:i:s] ' ) . $message . "\n";
232 if ( ! empty( $context ) ) {
233 $entry .= wp_json_encode( $context ) . "\n";
234 }
235 $entry .= "----------------------------------------\n";
236
237 @file_put_contents( $file, $entry, FILE_APPEND | LOCK_EX );
238 }
239
240 /**
241 * A protected log directory inside wp-content/uploads, never inside the plugin
242 * folder and never web-readable.
243 *
244 * @return string Absolute path without a trailing slash, or '' on failure.
245 */
246 function ebook_store_get_log_dir() {
247 static $dir = null;
248
249 if ( $dir !== null ) {
250 return $dir;
251 }
252
253 $uploads = wp_upload_dir();
254 if ( ! empty( $uploads['error'] ) ) {
255 $dir = '';
256 return $dir;
257 }
258
259 $dir = untrailingslashit( $uploads['basedir'] ) . '/ebook-store-logs';
260
261 if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) {
262 $dir = '';
263 return $dir;
264 }
265
266 // Belt and braces: block direct HTTP access on both Apache and any server
267 // that respects a directory index.
268 if ( ! file_exists( $dir . '/.htaccess' ) ) {
269 @file_put_contents( $dir . '/.htaccess', "Require all denied\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n" );
270 }
271 if ( ! file_exists( $dir . '/index.php' ) ) {
272 @file_put_contents( $dir . '/index.php', "<?php\n// Silence is golden.\n" );
273 }
274
275 return $dir;
276 }
277