Bearsampp 2026.9.23
Loading...
Searching...
No Matches
Symlinks Class Reference

Public Member Functions

 __construct ($root)

Static Public Member Functions

static createModuleSymlink ($module)
static deleteCurrentSymlinks ()
static initializePaths ()
static isSkippingSymlinkCreation ()
static safeRemoveSymlink ($path)
static setSkipSymlinkCreation (bool $skip)

Data Fields

const APACHE_SYMLINK = 'apache'
const BRUNO_SYMLINK = 'bruno'
const COMPOSER_SYMLINK = 'composer'
const GHOSTSCRIPT_SYMLINK = 'ghostscript'
const GIT_SYMLINK = 'git'
const MAILPIT_SYMLINK = 'mailpit'
const MARIADB_SYMLINK = 'mariadb'
const MEMCACHED_SYMLINK = 'memcached'
const MYSQL_SYMLINK = 'mysql'
const NGROK_SYMLINK = 'ngrok'
const NODEJS_SYMLINK = 'nodejs'
const PERL_SYMLINK = 'perl'
const PHP_SYMLINK = 'php'
const PHPMYADMIN_SYMLINK = 'phpmyadmin'
const PHPPGADMIN_SYMLINK = 'phppgadmin'
const POSTGRESQL_SYMLINK = 'postgresql'
const POWERSHELL_SYMLINK = 'powershell'
const PYTHON_SYMLINK = 'python'
const RUBY_SYMLINK = 'ruby'
const XLIGHT_SYMLINK = 'xlight'

Static Private Member Functions

static isPathWithinAllowedBase ($path)
static isSymlink ($path)

Static Private Attributes

static $root
static $skipSymlinkCreation = false

Detailed Description

Manages the creation and deletion of symbolic links for various components within the Bearsampp environment.

Definition at line 14 of file class.symlinks.php.

Constructor & Destructor Documentation

◆ __construct()

__construct ( $root)

Constructs a Symlinks object and initializes paths to current directories.

Parameters
Root$rootThe root object associated with the Bearsampp environment.

Definition at line 52 of file class.symlinks.php.

53 {
54 self::$root = $root;
56 }

References $root, and initializePaths().

Here is the call graph for this function:

Member Function Documentation

◆ createModuleSymlink()

createModuleSymlink ( $module)
static

Creates a symbolic link from the current path to the symlink path for a module. If the symlink already exists and points to the correct target, no action is taken.

Parameters
Module$moduleThe module instance.

Definition at line 97 of file class.symlinks.php.

98 {
99 $src = Path::formatWindowsPath($module->currentPath);
100 $dest = Path::formatWindowsPath($module->symlinkPath);
101
102 if (is_link($dest)) {
103 if (readlink($dest) === $src) {
104 return;
105 }
107 } elseif (file_exists($dest)) {
108 if (is_dir($dest)) {
109 Log::error('Cannot create symlink: a real directory exists at the destination: ' . $dest);
110
111 return;
112 }
113 Log::warning('Removing file at symlink location: ' . $dest);
114 if (!@unlink($dest)) {
115 Log::error('Failed to remove file at symlink location: ' . $dest);
116
117 return;
118 }
119 }
120
121 Batch::createSymlink($src, $dest);
122 }
static removeSymlink($link)
static createSymlink($src, $dest)
static warning($data, $file=null)
static error($data, $file=null)
static formatWindowsPath($path)

References Batch\createSymlink(), Log\error(), Path\formatWindowsPath(), Batch\removeSymlink(), and Log\warning().

Referenced by Module\reload().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ deleteCurrentSymlinks()

deleteCurrentSymlinks ( )
static

Deletes all symbolic links listed in the arrayOfCurrents. Logs each operation's success or failure.

This method iterates over a predefined list of symbolic link paths and attempts to delete each one. Uses strict safety checks to prevent accidental deletion of user data:

  • Only deletes symlinks or empty directories
  • Validates all paths are within Bearsampp managed directories
  • Refuses to perform recursive deletion
  • Does not follow or delete junction points with content

@global Root $bearsamppRoot The root object providing access to system paths. @global Core $bearsamppCore The core object providing core functionalities.

Definition at line 264 of file class.symlinks.php.

265 {
267
268 // Check to see if purging is necessary
269 $appsPath = Path::getAppsPath();
270 $binPath = Path::getBinPath();
271 $toolsPath = Path::getToolsPath();
272
273 $array = [
274 self::APACHE_SYMLINK => $binPath . '/apache/current',
275 self::BRUNO_SYMLINK => $toolsPath . '/bruno/current',
276 self::COMPOSER_SYMLINK => $toolsPath . '/composer/current',
277 self::GHOSTSCRIPT_SYMLINK => $toolsPath . '/ghostscript/current',
278 self::GIT_SYMLINK => $toolsPath . '/git/current',
279 self::MAILPIT_SYMLINK => $binPath . '/mailpit/current',
280 self::MARIADB_SYMLINK => $binPath . '/mariadb/current',
281 self::MEMCACHED_SYMLINK => $binPath . '/memcached/current',
282 self::MYSQL_SYMLINK => $binPath . '/mysql/current',
283 self::NGROK_SYMLINK => $toolsPath . '/ngrok/current',
284 self::NODEJS_SYMLINK => $binPath . '/nodejs/current',
285 self::PERL_SYMLINK => $toolsPath . '/perl/current',
286 self::PHP_SYMLINK => $binPath . '/php/current',
287 self::PHPMYADMIN_SYMLINK => $appsPath . '/phpmyadmin/current',
288 self::PHPPGADMIN_SYMLINK => $appsPath . '/phppgadmin/current',
289 self::POSTGRESQL_SYMLINK => $binPath . '/postgresql/current',
290 self::POWERSHELL_SYMLINK => $toolsPath . '/powershell/current',
291 self::PYTHON_SYMLINK => $toolsPath . '/python/current',
292 self::RUBY_SYMLINK => $toolsPath . '/ruby/current',
293 self::XLIGHT_SYMLINK => $binPath . '/xlight/current',
294 ];
295
296 // Fix for PHP 8.2: Add null checks before accessing array elements
297 if (!is_array($array) || empty($array)) {
298 Log::error('Current symlinks array is not initialized or empty.');
299
300 return;
301 }
302
303 // Purge "current" symlinks with safety checks
304 foreach ($array as $name => $path) {
305 // Skip if path is null
306 if (empty($path)) {
307 continue;
308 }
309
310 if (!file_exists($path) && !is_link($path)) {
311 // Log that the symlink was already missing
312 Log::trace('Symlink already deleted or missing: ' . $path);
313 continue;
314 }
315
316 // Use safe removal method with path validation and non-recursive guarantees
318 }
319 }
global $bearsamppRoot
global $bearsamppCore
static trace($data, $file=null)
static getToolsPath($aetrayPath=false)
static getBinPath($aetrayPath=false)
static getAppsPath($aetrayPath=false)

References $bearsamppCore, $bearsamppRoot, Log\error(), Path\getAppsPath(), Path\getBinPath(), Path\getToolsPath(), safeRemoveSymlink(), and Log\trace().

Referenced by ActionQuit\processWindow().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ initializePaths()

initializePaths ( )
static

Initializes paths for symlinks. This is called by the constructor or can be called manually.

Definition at line 62 of file class.symlinks.php.

63 {
64 // Path initialization logic can be added here if needed
65 // For now, it's a placeholder as requested by the issue to restore it.
66 }

Referenced by __construct().

Here is the caller graph for this function:

◆ isPathWithinAllowedBase()

isPathWithinAllowedBase ( $path)
staticprivate

Validates that a path is within allowed symlink directories. Prevents deletion of paths outside the Bearsampp managed directories.

Parameters
string$pathThe path to validate
Returns
bool True if path is within allowed directories, false otherwise

Definition at line 132 of file class.symlinks.php.

133 {
134 global $bearsamppRoot;
135
136 // Normalize paths for comparison
137 $normalizedPath = realpath($path);
138 if ($normalizedPath === false) {
139 Log::error('Failed to resolve path: ' . $path);
140
141 return false;
142 }
143
144 $allowedBases = [
145 realpath(Path::getAppsPath()),
146 realpath(Path::getBinPath()),
147 realpath(Path::getToolsPath())
148 ];
149
150 foreach ($allowedBases as $base) {
151 if ($base === false) {
152 continue;
153 }
154
155 // Ensure path starts with allowed base (with directory separator to prevent substring matches)
156 if (strpos($normalizedPath, $base . DIRECTORY_SEPARATOR) === 0 ||
157 $normalizedPath === $base) {
158 return true;
159 }
160 }
161
162 Log::error('Path is outside allowed symlink directories: ' . $path);
163
164 return false;
165 }

References $bearsamppRoot, Log\error(), Path\getAppsPath(), Path\getBinPath(), and Path\getToolsPath().

Here is the call graph for this function:

◆ isSkippingSymlinkCreation()

isSkippingSymlinkCreation ( )
static

Check if symlink creation is being skipped

Returns
bool True if symlink creation is skipped

Definition at line 86 of file class.symlinks.php.

86 : bool
87 {
88 return self::$skipSymlinkCreation;
89 }

Referenced by Module\reload().

Here is the caller graph for this function:

◆ isSymlink()

isSymlink ( $path)
staticprivate

Checks if a path is a symlink (not following the link). Uses lstat to avoid following symlinks.

Parameters
string$pathThe path to check
Returns
bool True if path is a symlink, false otherwise

Definition at line 175 of file class.symlinks.php.

176 {
177 return is_link($path);
178 }

◆ safeRemoveSymlink()

safeRemoveSymlink ( $path)
static

Safely removes a symlink or directory. CRITICAL: Use with caution as it may perform recursive deletion if the path is a directory.

Parameters
string$pathThe path to remove
Returns
bool True on success, false on failure

Definition at line 188 of file class.symlinks.php.

189 {
190 // Validate path is within allowed directories
191 if (!self::isPathWithinAllowedBase($path)) {
192 Log::error('Symlink removal blocked - path not in allowed directories: ' . $path);
193
194 return false;
195 }
196
197 // Check if path exists
198 if (!file_exists($path) && !self::isSymlink($path)) {
199 Log::trace('Symlink or directory already deleted or missing: ' . $path);
200
201 return false;
202 }
203
204 // If it's a directory (including junctions/directory-symlinks), use rmdir
205 if (is_dir($path)) {
206 // Attempt to remove as a symlink/junction first (non-recursive)
207 if (@rmdir($path)) {
208 Log::debug('Safely removed directory symlink/junction: ' . $path);
209
210 return true;
211 }
212
213 // If it failed and it's NOT a link, it's a real directory with content.
214 // We MUST NOT recursively delete it to avoid data loss.
215 if (!self::isSymlink($path)) {
216 Log::error('Symlink removal blocked - path is a real directory with content: ' . $path);
217
218 return false;
219 } else {
220 // If it's a link but rmdir failed, try unlink (e.g. file symlink)
221 if (@unlink($path)) {
222 Log::debug('Safely removed symlink via unlink: ' . $path);
223
224 return true;
225 }
226 }
227 }
228
229 // If it's a symlink but not a directory (e.g. file symlink), or if rmdir failed
230 if (self::isSymlink($path)) {
231 if (@unlink($path) || @rmdir($path)) {
232 Log::debug('Safely removed symlink: ' . $path);
233
234 return true;
235 }
236 }
237
238 if (self::isSymlink($path)) {
239 Log::error('Failed to remove symlink: ' . $path);
240
241 return false;
242 }
243
244 // Regular files should not be deleted here
245 Log::warning('Path is a regular file, not a symlink - refusing deletion: ' . $path);
246
247 return false;
248 }
static debug($data, $file=null)

References Log\debug(), Log\error(), Log\trace(), and Log\warning().

Referenced by deleteCurrentSymlinks(), and ActionQuit\verifySymlinksRemoved().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ setSkipSymlinkCreation()

setSkipSymlinkCreation ( bool $skip)
static

Skip symlink creation during module reload Useful for performance optimization during service checking phase

Parameters
bool$skipTrue to skip symlink creation
Returns
void

Definition at line 76 of file class.symlinks.php.

76 : void
77 {
78 self::$skipSymlinkCreation = $skip;
79 }

Referenced by ActionStartup\installServicesSequential().

Here is the caller graph for this function:

Field Documentation

◆ $root

$root
staticprivate

Definition at line 45 of file class.symlinks.php.

Referenced by __construct().

◆ $skipSymlinkCreation

$skipSymlinkCreation = false
staticprivate

Definition at line 40 of file class.symlinks.php.

◆ APACHE_SYMLINK

const APACHE_SYMLINK = 'apache'

Definition at line 16 of file class.symlinks.php.

◆ BRUNO_SYMLINK

const BRUNO_SYMLINK = 'bruno'

Definition at line 17 of file class.symlinks.php.

◆ COMPOSER_SYMLINK

const COMPOSER_SYMLINK = 'composer'

Definition at line 18 of file class.symlinks.php.

◆ GHOSTSCRIPT_SYMLINK

const GHOSTSCRIPT_SYMLINK = 'ghostscript'

Definition at line 19 of file class.symlinks.php.

◆ GIT_SYMLINK

const GIT_SYMLINK = 'git'

Definition at line 20 of file class.symlinks.php.

◆ MAILPIT_SYMLINK

const MAILPIT_SYMLINK = 'mailpit'

Definition at line 21 of file class.symlinks.php.

◆ MARIADB_SYMLINK

const MARIADB_SYMLINK = 'mariadb'

Definition at line 22 of file class.symlinks.php.

◆ MEMCACHED_SYMLINK

const MEMCACHED_SYMLINK = 'memcached'

Definition at line 23 of file class.symlinks.php.

◆ MYSQL_SYMLINK

const MYSQL_SYMLINK = 'mysql'

Definition at line 24 of file class.symlinks.php.

◆ NGROK_SYMLINK

const NGROK_SYMLINK = 'ngrok'

Definition at line 25 of file class.symlinks.php.

◆ NODEJS_SYMLINK

const NODEJS_SYMLINK = 'nodejs'

Definition at line 26 of file class.symlinks.php.

◆ PERL_SYMLINK

const PERL_SYMLINK = 'perl'

Definition at line 27 of file class.symlinks.php.

◆ PHP_SYMLINK

const PHP_SYMLINK = 'php'

Definition at line 28 of file class.symlinks.php.

◆ PHPMYADMIN_SYMLINK

const PHPMYADMIN_SYMLINK = 'phpmyadmin'

Definition at line 29 of file class.symlinks.php.

◆ PHPPGADMIN_SYMLINK

const PHPPGADMIN_SYMLINK = 'phppgadmin'

Definition at line 30 of file class.symlinks.php.

◆ POSTGRESQL_SYMLINK

const POSTGRESQL_SYMLINK = 'postgresql'

Definition at line 31 of file class.symlinks.php.

◆ POWERSHELL_SYMLINK

const POWERSHELL_SYMLINK = 'powershell'

Definition at line 32 of file class.symlinks.php.

◆ PYTHON_SYMLINK

const PYTHON_SYMLINK = 'python'

Definition at line 33 of file class.symlinks.php.

◆ RUBY_SYMLINK

const RUBY_SYMLINK = 'ruby'

Definition at line 34 of file class.symlinks.php.

◆ XLIGHT_SYMLINK

const XLIGHT_SYMLINK = 'xlight'

Definition at line 35 of file class.symlinks.php.


The documentation for this class was generated from the following file: