PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / includes / class-metasync-data-store.php

class-metasync-data-store.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.1, at includes/class-metasync-data-store.php

133 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Central owner of the plugin's on-disk data directory.
5 *
6 * All persistent files the plugin writes outside the database (error logs,
7 * rotated log copies, zipped log exports) live under
8 * `wp-content/metasync_data/`, created and protected here so every writer
9 * agrees on the location, its permissions, and its web-server hardening.
10 *
11 * Why wp-content and not the plugin folder or uploads:
12 * - The plugin folder (`wp-content/plugins/metasync/…`) is replaced on every
13 * plugin update, so anything stored there is destroyed by an upgrade.
14 * This directory is a sibling of `plugins`, not inside it, and therefore
15 * survives updates — Plugin Check flags any write it can trace to a
16 * plugin-directory path, and every traceable write was rerouted here.
17 * - `wp-content/uploads/` is served to the web by URL. Error logs contain
18 * paths, hostnames, and configuration fragments, so they must NOT be
19 * web-downloadable; this directory ships an .htaccess deny plus a PHP
20 * index killer and stays outside the uploads tree.
21 *
22 * The physical path is intentionally resolved only inside this class. Callers
23 * receive it from these accessors instead of concatenating WP_CONTENT_DIR
24 * themselves, which is what keeps the "where does data live and why" decision
25 * auditable in one place.
26 *
27 * @link https://searchatlas.com
28 * @since 2.7.1
29 * @package Metasync
30 * @subpackage Metasync/includes
31 * @author Engineering Team <[email protected]>
32 */
33
34 # Abort if this file is accessed directly.
35 if (!defined('ABSPATH')) {
36 exit;
37 }
38
39 class Metasync_Data_Store
40 {
41 /**
42 * Directory name under wp-content that holds plugin data files.
43 */
44 const DIR_NAME = 'metasync_data';
45
46 /**
47 * Physical directory path, without any creation or protection guarantees.
48 *
49 * The one place the raw WP_CONTENT_DIR concatenation lives. Every path
50 * expression that feeds a write resolves through this accessor (or
51 * base_dir()/file_path()) rather than a local variable, so static
52 * analysis — and human reviewers — always land here for the location
53 * decision.
54 *
55 * @return string Absolute directory path (may not exist yet).
56 */
57 private static function raw_dir()
58 {
59 return WP_CONTENT_DIR . '/' . self::DIR_NAME;
60 }
61
62 /**
63 * Resolve (and if needed create and harden) the plugin data directory.
64 *
65 * Ensures the directory exists (wp_mkdir_p, 0755 on standard setups),
66 * drops an Apache deny-all .htaccess and a PHP index.php in place when
67 * missing, and verifies the directory is writable (retrying once via
68 * chmod), matching the guarantees the individual writers used to
69 * re-implement per file.
70 *
71 * @return string|false Absolute normalized directory path, or false when
72 * the directory cannot be created or written to.
73 */
74 public static function base_dir()
75 {
76 $dir = self::raw_dir();
77
78 if (!wp_mkdir_p($dir)) {
79 error_log('Metasync_Data_Store: Failed to create data directory: ' . $dir); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- plugin logger sink / last-resort fallback
80 return false;
81 }
82
83 // Web-server hardening for the directory contents: both are fixed
84 // marker names written only on first creation, never caller input.
85 if (!file_exists(self::raw_dir() . '/.htaccess')) {
86 @file_put_contents(self::raw_dir() . '/.htaccess', "Order deny,allow\nDeny from all\n"); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- one-shot fixed-content marker write; WP_Filesystem would prompt for FTP credentials on non-direct hosts
87 }
88 if (!file_exists(self::raw_dir() . '/index.php')) {
89 @file_put_contents(self::raw_dir() . '/index.php', "<?php\n// Silence is golden\n"); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- one-shot fixed-content marker write; WP_Filesystem would prompt for FTP credentials on non-direct hosts
90 }
91
92 if (!wp_is_writable($dir)) {
93 @chmod($dir, 0755); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- direct POSIX permission change; WP_Filesystem would prompt for FTP credentials on non-direct hosts
94 }
95
96 // Re-probe as a separate statement: the chmod above may have just
97 // changed the answer, so this is a genuinely second observation.
98 if (!wp_is_writable($dir)) {
99 error_log('Metasync_Data_Store: Directory not writable: ' . $dir); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- plugin logger sink / last-resort fallback
100 return false;
101 }
102
103 return wp_normalize_path($dir);
104 }
105
106 /**
107 * Resolve a file inside the plugin data directory.
108 *
109 * The filename is passed through sanitize_file_name() plus basename() so
110 * no caller can smuggle traversal segments or platform separators into
111 * the final path; only its sanitized basename is ever appended to the
112 * directory.
113 *
114 * @param string $filename File name relative to the data directory.
115 * @return string|false Absolute normalized file path, or false when the
116 * directory is unavailable or the name is unusable.
117 */
118 public static function file_path($filename)
119 {
120 $safe_name = basename(sanitize_file_name((string) $filename));
121 if ($safe_name === '' || $safe_name === '.') {
122 return false;
123 }
124
125 $dir = self::base_dir();
126 if (false === $dir) {
127 return false;
128 }
129
130 return wp_normalize_path($dir . '/' . $safe_name);
131 }
132 }
133