} */ private static function fileGetContentsWithTransientRetry($path): array { $warnings = array(); for ($attempt = 1; $attempt <= self::FILE_READ_MAX_ATTEMPTS; $attempt++) { $attemptWarnings = array(); set_error_handler( static function(int $errno, string $errstr, string $errfile = '', int $errline = 0) use (&$attemptWarnings): bool { if (($errno & (E_WARNING | E_USER_WARNING)) === 0) { return false; } $attemptWarnings[] = array( 'errno' => $errno, 'message' => $errstr, 'file' => $errfile, 'line' => $errline, ); return true; }, E_WARNING | E_USER_WARNING ); try { $contents = file_get_contents($path); } finally { restore_error_handler(); } if ($contents !== false) { return array( 'contents' => $contents, 'warnings' => array_merge($warnings, $attemptWarnings), ); } $warnings = array_merge($warnings, $attemptWarnings); if (!self::isTransientFileReadWarning($attemptWarnings)) { break; } if ($attempt < self::FILE_READ_MAX_ATTEMPTS) { usleep(self::FILE_READ_RETRY_BASE_US * $attempt); } } return array( 'contents' => false, 'warnings' => $warnings, ); } /** * @param array $warnings * @return bool */ private static function isTransientFileReadWarning(array $warnings): bool { foreach ($warnings as $warning) { if (strpos(strtolower($warning['message']), 'interrupted system call') !== false) { return true; } } return false; } /** * @param array $warnings * @return string */ private static function formatFileReadWarnings(array $warnings): string { if (empty($warnings)) { return ''; } $parts = array(); foreach ($warnings as $warning) { $parts[] = sprintf( '[%d] %s in %s:%d', $warning['errno'], $warning['message'], $warning['file'], $warning['line'] ); } return "\n file_get_contents warnings: " . implode(' | ', $parts); } /** * Build the BEGIN/END banner pair that brackets file contents in the * debug-log dump so an admin reading a debug bundle can see where each * embedded artifact starts and ends. SQL gets /* ... *\/ comments; * HTML gets HTML comments; other types get a generic marker. * * @param string $filePath * @param bool $appendExtraData * @return array */ private static function getDataSupplement(string $filePath, bool $appendExtraData = true): array { $path = strtolower($filePath); // remove the first part of the path because some people don't want to see // it in the log file. $homepath = (string) dirname(ABSPATH); $beginningOfPath = (string) substr($path, 0, strlen($homepath)); if (strtolower($beginningOfPath) === strtolower($homepath)) { $path = (string) substr($path, strlen($homepath)); } $supplement = array(); if (!$appendExtraData) { $supplement['prefix'] = ''; $supplement['suffix'] = ''; } else if (self::endsWithCaseInsensitive($path, '.sql')) { $supplement['prefix'] = "\n/* ------------------ " . $filePath . " BEGIN ----- */ \n"; $supplement['suffix'] = "\n/* ------------------ " . $filePath . " END ----- */ \n"; } else if (self::endsWithCaseInsensitive($path, '.html')) { $supplement['prefix'] = "\n \n"; $supplement['suffix'] = "\n \n"; } else { $supplement['prefix'] = "\n/* ------------------ " . $filePath . " BEGIN unknown file type in " . __CLASS__ . '::' . __FUNCTION__ . "() ----- */ \n"; $supplement['suffix'] = "\n/* ------------------ " . $filePath . " END unknown file type in " . __CLASS__ . '::' . __FUNCTION__ . "() ----- */ \n"; } return $supplement; } private static function endsWithCaseInsensitive(string $haystack, string $needle): bool { $length = strlen($needle); if (strlen($haystack) < $length) { return false; } return strtolower(substr($haystack, -$length)) === strtolower($needle); } /** Deletes the existing file at $filePath and puts the URL contents in it's place. * @param string $url * @param string $filePath * @return void */ static function readURLtoFile(string $url, string $filePath): void { $abj404logging = abj_service('logging'); self::safeUnlink($filePath); // if we can't read the file that way then try curl. if (function_exists('curl_init')) { try { //This is the file where we save the information $destinationFileWriteHandle = fopen($filePath, 'w+'); //Here is the file we are downloading, replace spaces with %20 $ch = curl_init(str_replace(" ", "%20", $url)); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 ' . '(KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36 (404 Solution WordPress Plugin)'); curl_setopt($ch, CURLOPT_TIMEOUT, 10); // write curl response to file if (is_resource($destinationFileWriteHandle)) { curl_setopt($ch, CURLOPT_FILE, $destinationFileWriteHandle); } curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // get curl response curl_exec($ch); if (is_resource($destinationFileWriteHandle)) { fclose($destinationFileWriteHandle); } if (file_exists($filePath) && filesize($filePath) > 0) { return; } } catch (Exception $e) { $abj404logging->debugMessage("curl didn't work for downloading a URL. " . $e->getMessage()); } } // Fallback to file_put_contents if curl didn't work or isn't available self::safeUnlink($filePath); try { $fileHandle = @fopen($url, 'r'); if ($fileHandle === false) { $abj404logging->errorMessage("Failed to open URL for reading: " . $url); return; } $result = file_put_contents($filePath, $fileHandle); fclose($fileHandle); if ($result === false) { $abj404logging->errorMessage("Failed to write file: " . $filePath); } } catch (Exception $e) { $abj404logging->errorMessage("Failed to download URL to file. URL: " . $url . ", Error: " . $e->getMessage()); } } private static function logWarning(string $message): void { $logger = function_exists('abj_service_optional') ? abj_service_optional('logging') : null; if (is_object($logger) && method_exists($logger, 'warn')) { $logger->warn($message); return; } abj404_logPhpFallback('service-resolution-fallback', $message); } }