Bearsampp 2026.7.11
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 96 of file class.symlinks.php.

97 {
98 $src = Path::formatWindowsPath($module->currentPath);
99 $dest = Path::formatWindowsPath($module->symlinkPath);
100
101 if (is_link($dest)) {
102 if (readlink($dest) === $src) {
103 return;
104 }
106 } elseif (file_exists($dest)) {
107 if (is_dir($dest)) {
108 Log::error('Cannot create symlink: a real directory exists at the destination: ' . $dest);
109 return;
110 }
111 Log::warning('Removing file at symlink location: ' . $dest);
112 if (!@unlink($dest)) {
113 Log::error('Failed to remove file at symlink location: ' . $dest);
114 return;
115 }
116 }
117
118 Batch::createSymlink($src, $dest);
119 }
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 248 of file class.symlinks.php.

249 {
251
252 // Check to see if purging is necessary
253 $appsPath = Path::getAppsPath();
254 $binPath = Path::getBinPath();
255 $toolsPath = Path::getToolsPath();
256
257 $array = [
258 self::APACHE_SYMLINK => $binPath . '/apache/current',
259 self::BRUNO_SYMLINK => $toolsPath . '/bruno/current',
260 self::COMPOSER_SYMLINK => $toolsPath . '/composer/current',
261 self::GHOSTSCRIPT_SYMLINK => $toolsPath . '/ghostscript/current',
262 self::GIT_SYMLINK => $toolsPath . '/git/current',
263 self::MAILPIT_SYMLINK => $binPath . '/mailpit/current',
264 self::MARIADB_SYMLINK => $binPath . '/mariadb/current',
265 self::MEMCACHED_SYMLINK => $binPath . '/memcached/current',
266 self::MYSQL_SYMLINK => $binPath . '/mysql/current',
267 self::NGROK_SYMLINK => $toolsPath . '/ngrok/current',
268 self::NODEJS_SYMLINK => $binPath . '/nodejs/current',
269 self::PERL_SYMLINK => $toolsPath . '/perl/current',
270 self::PHP_SYMLINK => $binPath . '/php/current',
271 self::PHPMYADMIN_SYMLINK => $appsPath . '/phpmyadmin/current',
272 self::PHPPGADMIN_SYMLINK => $appsPath . '/phppgadmin/current',
273 self::POSTGRESQL_SYMLINK => $binPath . '/postgresql/current',
274 self::POWERSHELL_SYMLINK => $toolsPath . '/powershell/current',
275 self::PYTHON_SYMLINK => $toolsPath . '/python/current',
276 self::RUBY_SYMLINK => $toolsPath . '/ruby/current',
277 self::XLIGHT_SYMLINK => $binPath . '/xlight/current',
278 ];
279
280 // Fix for PHP 8.2: Add null checks before accessing array elements
281 if (!is_array($array) || empty($array)) {
282 Log::error('Current symlinks array is not initialized or empty.');
283 return;
284 }
285
286 // Purge "current" symlinks with safety checks
287 foreach ($array as $name => $path) {
288 // Skip if path is null
289 if (empty($path)) {
290 continue;
291 }
292
293 if (!file_exists($path) && !is_link($path)) {
294 // Log that the symlink was already missing
295 Log::trace('Symlink already deleted or missing: ' . $path);
296 continue;
297 }
298
299 // Use safe removal method with path validation and non-recursive guarantees
301 }
302 }
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 128 of file class.symlinks.php.

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

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 85 of file class.symlinks.php.

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

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 168 of file class.symlinks.php.

169 {
170 return is_link($path);
171 }

◆ 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 180 of file class.symlinks.php.

181 {
182 // Validate path is within allowed directories
183 if (!self::isPathWithinAllowedBase($path)) {
184 Log::error('Symlink removal blocked - path not in allowed directories: ' . $path);
185 return false;
186 }
187
188 // Check if path exists
189 if (!file_exists($path) && !is_link($path)) {
190 Log::trace('Symlink or directory already deleted or missing: ' . $path);
191 return false;
192 }
193
194 // If it's a directory (including junctions/directory-symlinks), use rmdir
195 if (is_dir($path)) {
196 // Attempt to remove as a symlink/junction first (non-recursive)
197 if (@rmdir($path)) {
198 Log::debug('Safely removed directory symlink/junction: ' . $path);
199 return true;
200 }
201
202 // If it failed and it's NOT a link, it's a real directory with content.
203 // We MUST NOT recursively delete it to avoid data loss.
204 if (!is_link($path)) {
205 Log::error('Symlink removal blocked - path is a real directory with content: ' . $path);
206 return false;
207 } else {
208 // If it's a link but rmdir failed, try unlink (e.g. file symlink)
209 if (@unlink($path)) {
210 Log::debug('Safely removed symlink via unlink: ' . $path);
211 return true;
212 }
213 }
214 }
215
216 // If it's a symlink but not a directory (e.g. file symlink), or if rmdir failed
217 if (is_link($path)) {
218 if (@unlink($path) || @rmdir($path)) {
219 Log::debug('Safely removed symlink: ' . $path);
220 return true;
221 }
222 }
223
224 if (is_link($path)) {
225 Log::error('Failed to remove symlink: ' . $path);
226 return false;
227 }
228
229 // Regular files should not be deleted here
230 Log::warning('Path is a regular file, not a symlink - refusing deletion: ' . $path);
231 return false;
232 }
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 75 of file class.symlinks.php.

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

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: