';
foreach ($wp_filesystem->errors->get_error_messages() as $message) show_message($message);
echo '
';
echo '
';
exit;
}
}
}
/**
* Get the html of "Web-server disk space" line which resides above of the existing backup table
*
* @param Boolean $will_immediately_calculate_disk_space Whether disk space should be counted now or when user click Refresh link
*
* @return String Web server disk space html to render
*/
public static function web_server_disk_space($will_immediately_calculate_disk_space = true) {
if ($will_immediately_calculate_disk_space) {
$disk_space_used = self::get_disk_space_used('updraft', 'numeric');
if ($disk_space_used > apply_filters('updraftplus_display_usage_line_threshold_size', 104857600)) { // 104857600 = 100 MB = (100 * 1024 * 1024)
$disk_space_text = UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($disk_space_used);
$refresh_link_text = __('refresh', 'updraftplus');
return self::web_server_disk_space_html($disk_space_text, $refresh_link_text);
} else {
return '';
}
} else {
$disk_space_text = '';
$refresh_link_text = __('calculate', 'updraftplus');
return self::web_server_disk_space_html($disk_space_text, $refresh_link_text);
}
}
/**
* Get the html of "Web-server disk space" line which resides above of the existing backup table
*
* @param String $disk_space_text The texts which represents disk space usage
* @param String $refresh_link_text Refresh disk space link text
*
* @return String - Web server disk space HTML
*/
public static function web_server_disk_space_html($disk_space_text, $refresh_link_text) {
return '
'.__('Web-server disk space in use by UpdraftPlus', 'updraftplus').':'.$disk_space_text.''.$refresh_link_text.'
';
}
/**
* Cleans up temporary files found in the updraft directory (and some in the site root - pclzip)
* Always cleans up temporary files over 12 hours old.
* With parameters, also cleans up those.
* Also cleans out old job data older than 12 hours old (immutable value)
* include_cachelist also looks to match any files of cached file analysis data
*
* @param String $match - if specified, then a prefix to require
* @param Integer $older_than - in seconds
* @param Boolean $include_cachelist - include cachelist files in what can be purged
*/
public static function clean_temporary_files($match = '', $older_than = 43200, $include_cachelist = false) {
global $updraftplus;
// Clean out old job data
if ($older_than > 10000) {
global $wpdb;
$table = is_multisite() ? $wpdb->sitemeta : $wpdb->options;
$key_column = is_multisite() ? 'meta_key' : 'option_name';
$value_column = is_multisite() ? 'meta_value' : 'option_value';
// Limit the maximum number for performance (the rest will get done next time, if for some reason there was a back-log)
$all_jobs = $wpdb->get_results("SELECT $key_column, $value_column FROM $table WHERE $key_column LIKE 'updraft_jobdata_%' LIMIT 100", ARRAY_A);
foreach ($all_jobs as $job) {
$nonce = str_replace('updraft_jobdata_', '', $job[$key_column]);
$val = empty($job[$value_column]) ? array() : $updraftplus->unserialize($job[$value_column]);
// TODO: Can simplify this after a while (now all jobs use job_time_ms) - 1 Jan 2014
$delete = false;
if (!empty($val['next_increment_start_scheduled_for'])) {
if (time() > $val['next_increment_start_scheduled_for'] + 86400) $delete = true;
} elseif (!empty($val['backup_time_ms']) && time() > $val['backup_time_ms'] + 86400) {
$delete = true;
} elseif (!empty($val['job_time_ms']) && time() > $val['job_time_ms'] + 86400) {
$delete = true;
} elseif (!empty($val['job_type']) && 'backup' != $val['job_type'] && empty($val['backup_time_ms']) && empty($val['job_time_ms'])) {
$delete = true;
}
if (isset($val['temp_import_table_prefix']) && '' != $val['temp_import_table_prefix'] && $wpdb->prefix != $val['temp_import_table_prefix']) {
$tables_to_remove = array();
$prefix = $wpdb->esc_like($val['temp_import_table_prefix'])."%";
$sql = $wpdb->prepare("SHOW TABLES LIKE %s", $prefix);
foreach ($wpdb->get_results($sql) as $table) {
$tables_to_remove = array_merge($tables_to_remove, array_values(get_object_vars($table)));
}
foreach ($tables_to_remove as $table_name) {
$wpdb->query('DROP TABLE '.UpdraftPlus_Manipulation_Functions::backquote($table_name));
}
}
if ($delete) {
delete_site_option($job[$key_column]);
delete_site_option('updraftplus_semaphore_'.$nonce);
}
}
}
$updraft_dir = $updraftplus->backups_dir_location();
$now_time = time();
$files_deleted = 0;
$include_cachelist = defined('DOING_CRON') && DOING_CRON && doing_action('updraftplus_clean_temporary_files') ? true : $include_cachelist;
if ($handle = opendir($updraft_dir)) {
while (false !== ($entry = readdir($handle))) {
$manifest_match = preg_match("/updraftplus-manifest\.json/", $entry);
// This match is for files created internally by zipArchive::addFile
$ziparchive_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.(?:[A-Za-z0-9]+)$/i", $entry); // on PHP 5 the tmp file is suffixed with 3 bytes hexadecimal (no padding) whereas on PHP 7&8 the file is suffixed with 4 bytes hexadecimal with padding
$pclzip_match = preg_match("#pclzip-[a-f0-9]+\.(?:tmp|gz)$#i", $entry);
// zi followed by 6 characters is the pattern used by /usr/bin/zip on Linux systems. It's safe to check for, as we have nothing else that's going to match that pattern.
$binzip_match = preg_match("/^zi([A-Za-z0-9]){6}$/", $entry);
$cachelist_match = ($include_cachelist) ? preg_match("/-cachelist-.*(?:info|\.tmp)$/i", $entry) : false;
$browserlog_match = preg_match('/^log\.[0-9a-f]+-browser\.txt$/', $entry);
$downloader_client_match = preg_match("/$match([0-9]+)?\.zip\.tmp\.(?:[A-Za-z0-9]+)\.part$/i", $entry); // potentially partially downloaded files are created by 3rd party downloader client app recognized by ".part" extension at the end of the backup file name (e.g. .zip.tmp.3b9r8r.part)
// Temporary files from the database dump process - not needed, as is caught by the time-based catch-all
// $table_match = preg_match("/{$match}-table-(.*)\.table(\.tmp)?\.gz$/i", $entry);
// The gz goes in with the txt, because we *don't* want to reap the raw .txt files
if ((preg_match("/$match\.(tmp|table|txt\.gz)(\.gz)?$/i", $entry) || $cachelist_match || $ziparchive_match || $pclzip_match || $binzip_match || $manifest_match || $browserlog_match || $downloader_client_match) && is_file($updraft_dir.'/'.$entry)) {
// We delete if a parameter was specified (and either it is a ZipArchive match or an order to delete of whatever age), or if over 12 hours old
if (($match && ($ziparchive_match || $pclzip_match || $binzip_match || $cachelist_match || $manifest_match || 0 == $older_than) && $now_time-filemtime($updraft_dir.'/'.$entry) >= $older_than) || $now_time-filemtime($updraft_dir.'/'.$entry)>43200) {
$skip_dblog = (0 == $files_deleted % 25) ? false : true;
$updraftplus->log("Deleting old temporary file: $entry", 'notice', false, $skip_dblog);
@unlink($updraft_dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
$files_deleted++;
}
} elseif (preg_match('/^log\.[0-9a-f]+\.txt$/', $entry) && $now_time-filemtime($updraft_dir.'/'.$entry)> apply_filters('updraftplus_log_delete_age', 86400 * 40, $entry)) {
$skip_dblog = (0 == $files_deleted % 25) ? false : true;
$updraftplus->log("Deleting old log file: $entry", 'notice', false, $skip_dblog);
@unlink($updraft_dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
$files_deleted++;
}
}
@closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
}
// Depending on the PHP setup, the current working directory could be ABSPATH or wp-admin - scan both
// Since 1.9.32, we set them to go into $updraft_dir, so now we must check there too. Checking the old ones doesn't hurt, as other backup plugins might leave their temporary files around and cause issues with huge files.
foreach (array(ABSPATH, ABSPATH.'wp-admin/', $updraft_dir.'/') as $path) {
if ($handle = opendir($path)) {
while (false !== ($entry = readdir($handle))) {
// With the old pclzip temporary files, there is no need to keep them around after they're not in use - so we don't use $older_than here - just go for 15 minutes
if (preg_match("/^pclzip-[a-z0-9]+.tmp$/", $entry) && $now_time-filemtime($path.$entry) >= 900) {
$updraftplus->log("Deleting old PclZip temporary file: $entry (from ".basename($path).")");
@unlink($path.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
}
}
@closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
}
}
}
/**
* Find out whether we really can write to a particular folder
*
* @param String $dir - the folder path
*
* @return Boolean - the result
*/
public static function really_is_writable($dir) {
// Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
if (!@is_writable($dir)) return false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
// Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
$rand_file = "$dir/test-".md5(rand().time()).".txt";
while (file_exists($rand_file)) {
$rand_file = "$dir/test-".md5(rand().time()).".txt";
}
$ret = @file_put_contents($rand_file, 'testing...');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
@unlink($rand_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
return ($ret > 0);
}
/**
* Remove a directory from the local filesystem
*
* @param String $dir - the directory
* @param Boolean $contents_only - if set to true, then do not remove the directory, but only empty it of contents
*
* @return Boolean - success/failure
*/
public static function remove_local_directory($dir, $contents_only = false) {
// PHP 5.3+ only
// foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) {
// $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname());
// }
// return rmdir($dir);
if ($handle = @opendir($dir)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
while (false !== ($entry = readdir($handle))) {
if ('.' !== $entry && '..' !== $entry) {
if (is_dir($dir.'/'.$entry)) {
self::remove_local_directory($dir.'/'.$entry, false);
} else {
@unlink($dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
}
}
}
@closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
}
return $contents_only ? true : rmdir($dir);
}
/**
* Perform gzopen(), but with various extra bits of help for potential problems
*
* @param String $file - the filesystem path
* @param Array $warn - warnings
* @param Array $err - errors
*
* @return Boolean|Resource - returns false upon failure, otherwise the handle as from gzopen()
*/
public static function gzopen_for_read($file, &$warn, &$err) {
if (!function_exists('gzopen') || !function_exists('gzread')) {
$missing = '';
if (!function_exists('gzopen')) $missing .= 'gzopen';
if (!function_exists('gzread')) $missing .= ($missing) ? ', gzread' : 'gzread';
$err[] = sprintf(__("Your web server's PHP installation has these functions disabled: %s.", 'updraftplus'), $missing).' '.sprintf(__('Your hosting company must enable these functions before %s can work.', 'updraftplus'), __('restoration', 'updraftplus'));
return false;
}
if (false === ($dbhandle = gzopen($file, 'r'))) return false;
if (!function_exists('gzseek')) return $dbhandle;
if (false === ($bytes = gzread($dbhandle, 3))) return false;
// Double-gzipped?
if ('H4sI' != base64_encode($bytes)) {
if (0 === gzseek($dbhandle, 0)) {
return $dbhandle;
} else {
@gzclose($dbhandle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
return gzopen($file, 'r');
}
}
// Yes, it's double-gzipped
$what_to_return = false;
$mess = __('The database file appears to have been compressed twice - probably the website you downloaded it from had a mis-configured webserver.', 'updraftplus');
$messkey = 'doublecompress';
$err_msg = '';
if (false === ($fnew = fopen($file.".tmp", 'w')) || !is_resource($fnew)) {
@gzclose($dbhandle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
$err_msg = __('The attempt to undo the double-compression failed.', 'updraftplus');
} else {
@fwrite($fnew, $bytes);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
$emptimes = 0;
while (!gzeof($dbhandle)) {
$bytes = @gzread($dbhandle, 262144);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
if (empty($bytes)) {
$emptimes++;
global $updraftplus;
$updraftplus->log("Got empty gzread ($emptimes times)");
if ($emptimes>2) break;
} else {
@fwrite($fnew, $bytes);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
}
}
gzclose($dbhandle);
fclose($fnew);
// On some systems (all Windows?) you can't rename a gz file whilst it's gzopened
if (!rename($file.".tmp", $file)) {
$err_msg = __('The attempt to undo the double-compression failed.', 'updraftplus');
} else {
$mess .= ' '.__('The attempt to undo the double-compression succeeded.', 'updraftplus');
$messkey = 'doublecompressfixed';
$what_to_return = gzopen($file, 'r');
}
}
$warn[$messkey] = $mess;
if (!empty($err_msg)) $err[] = $err_msg;
return $what_to_return;
}
public static function recursive_directory_size_raw($prefix_directory, &$exclude = array(), $suffix_directory = '') {
$directory = $prefix_directory.('' == $suffix_directory ? '' : '/'.$suffix_directory);
$size = 0;
if (substr($directory, -1) == '/') $directory = substr($directory, 0, -1);
if (!file_exists($directory) || !is_dir($directory) || !is_readable($directory)) return -1;
if (file_exists($directory.'/.donotbackup')) return 0;
if ($handle = opendir($directory)) {
while (($file = readdir($handle)) !== false) {
if ('.' != $file && '..' != $file) {
$spath = ('' == $suffix_directory) ? $file : $suffix_directory.'/'.$file;
if (false !== ($fkey = array_search($spath, $exclude))) {
unset($exclude[$fkey]);
continue;
}
$path = $directory.'/'.$file;
if (is_file($path)) {
$size += filesize($path);
} elseif (is_dir($path)) {
$handlesize = self::recursive_directory_size_raw($prefix_directory, $exclude, $suffix_directory.('' == $suffix_directory ? '' : '/').$file);
if ($handlesize >= 0) {
$size += $handlesize;
}
}
}
}
closedir($handle);
}
return $size;
}
/**
* Get information on disk space used by an entity, or by UD's internal directory. Returns as a human-readable string.
*
* @param String $entity - the entity (e.g. 'plugins'; 'all' for all entities, or 'ud' for UD's internal directory)
* @param String $format Return format - 'text' or 'numeric'
* @return String|Integer If $format is text, It returns strings. Otherwise integer value.
*/
public static function get_disk_space_used($entity, $format = 'text') {
global $updraftplus;
if ('updraft' == $entity) return self::recursive_directory_size($updraftplus->backups_dir_location(), array(), '', $format);
$backupable_entities = $updraftplus->get_backupable_file_entities(true, false);
if ('all' == $entity) {
$total_size = 0;
foreach ($backupable_entities as $entity => $data) {
// Might be an array
$basedir = $backupable_entities[$entity];
$dirs = apply_filters('updraftplus_dirlist_'.$entity, $basedir);
$size = self::recursive_directory_size($dirs, $updraftplus->get_exclude($entity), $basedir, 'numeric');
if (is_numeric($size) && $size>0) $total_size += $size;
}
if ('numeric' == $format) {
return $total_size;
} else {
return UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($total_size);
}
} elseif (!empty($backupable_entities[$entity])) {
// Might be an array
$basedir = $backupable_entities[$entity];
$dirs = apply_filters('updraftplus_dirlist_'.$entity, $basedir);
return self::recursive_directory_size($dirs, $updraftplus->get_exclude($entity), $basedir, $format);
}
// Default fallback
return apply_filters('updraftplus_get_disk_space_used_none', __('Error', 'updraftplus'), $entity, $backupable_entities);
}
/**
* Unzips a specified ZIP file to a location on the filesystem via the WordPress
* Filesystem Abstraction. Forked from WordPress core in version 5.1-alpha-44182,
* to allow us to provide feedback on progress.
*
* Assumes that WP_Filesystem() has already been called and set up. Does not extract
* a root-level __MACOSX directory, if present.
*
* Attempts to increase the PHP memory limit before uncompressing. However,
* the most memory required shouldn't be much larger than the archive itself.
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param String $file - Full path and filename of ZIP archive.
* @param String $to - Full path on the filesystem to extract archive to.
* @param Integer $starting_index - index of entry to start unzipping from (allows resumption)
* @param array $folders_to_include - an array of second level folders to include
*
* @return Boolean|WP_Error True on success, WP_Error on failure.
*/
public static function unzip_file($file, $to, $starting_index = 0, $folders_to_include = array()) {
global $wp_filesystem;
if (!$wp_filesystem || !is_object($wp_filesystem)) {
return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
}
// Unzip can use a lot of memory, but not this much hopefully.
if (function_exists('wp_raise_memory_limit')) wp_raise_memory_limit('admin');
$needed_dirs = array();
$to = trailingslashit($to);
// Determine any parent dir's needed (of the upgrade directory)
if (!$wp_filesystem->is_dir($to)) { // Only do parents if no children exist
$path = preg_split('![/\\\]!', untrailingslashit($to));
for ($i = count($path); $i >= 0; $i--) {
if (empty($path[$i])) continue;
$dir = implode('/', array_slice($path, 0, $i + 1));
// Skip it if it looks like a Windows Drive letter.
if (preg_match('!^[a-z]:$!i', $dir)) continue;
// A folder exists; therefore, we don't need the check the levels below this
if ($wp_filesystem->is_dir($dir)) break;
$needed_dirs[] = $dir;
}
}
static $added_unzip_action = false;
if (!$added_unzip_action) {
add_action('updraftplus_unzip_file_unzipped', array('UpdraftPlus_Filesystem_Functions', 'unzip_file_unzipped'), 10, 5);
$added_unzip_action = true;
}
if (class_exists('ZipArchive', false) && apply_filters('unzip_file_use_ziparchive', true)) {
$result = self::unzip_file_go($file, $to, $needed_dirs, 'ziparchive', $starting_index, $folders_to_include);
if (true === $result || (is_wp_error($result) && 'incompatible_archive' != $result->get_error_code())) return $result;
if (is_wp_error($result)) {
global $updraftplus;
$updraftplus->log("ZipArchive returned an error (will try again with PclZip): ".$result->get_error_code());
}
}
// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
// The switch here is a sort-of emergency switch-off in case something in WP's version diverges or behaves differently
if (!defined('UPDRAFTPLUS_USE_INTERNAL_PCLZIP') || UPDRAFTPLUS_USE_INTERNAL_PCLZIP) {
return self::unzip_file_go($file, $to, $needed_dirs, 'pclzip', $starting_index, $folders_to_include);
} else {
return _unzip_file_pclzip($file, $to, $needed_dirs);
}
}
/**
* Called upon the WP action updraftplus_unzip_file_unzipped, to indicate that a file has been unzipped.
*
* @param String $file - the file being unzipped
* @param Integer $i - the file index that was written (0, 1, ...)
* @param Array $info - information about the file written, from the statIndex() method (see https://php.net/manual/en/ziparchive.statindex.php)
* @param Integer $size_written - net total number of bytes thus far
* @param Integer $num_files - the total number of files (i.e. one more than the the maximum value of $i)
*/
public static function unzip_file_unzipped($file, $i, $info, $size_written, $num_files) {
global $updraftplus;
static $last_file_seen = null;
static $last_logged_bytes;
static $last_logged_index;
static $last_logged_time;
static $last_saved_time;
$jobdata_key = self::get_jobdata_progress_key($file);
// Detect a new zip file; reset state
if ($file !== $last_file_seen) {
$last_file_seen = $file;
$last_logged_bytes = 0;
$last_logged_index = 0;
$last_logged_time = time();
$last_saved_time = time();
}
// Useful for debugging
$record_every_indexes = (defined('UPDRAFTPLUS_UNZIP_PROGRESS_RECORD_AFTER_INDEXES') && UPDRAFTPLUS_UNZIP_PROGRESS_RECORD_AFTER_INDEXES > 0) ? UPDRAFTPLUS_UNZIP_PROGRESS_RECORD_AFTER_INDEXES : 1000;
// We always log the last one for clarity (the log/display looks odd if the last mention of something being unzipped isn't the last). Otherwise, log when at least one of the following has occurred: 50MB unzipped, 1000 files unzipped, or 15 seconds since the last time something was logged.
if ($i >= $num_files -1 || $size_written > $last_logged_bytes + 100 * 1048576 || $i > $last_logged_index + $record_every_indexes || time() > $last_logged_time + 15) {
$updraftplus->jobdata_set($jobdata_key, array('index' => $i, 'info' => $info, 'size_written' => $size_written));
$updraftplus->log(sprintf(__('Unzip progress: %d out of %d files', 'updraftplus').' (%s, %s)', $i+1, $num_files, UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size_written), $info['name']), 'notice-restore');
$updraftplus->log(sprintf('Unzip progress: %d out of %d files (%s, %s)', $i+1, $num_files, UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size_written), $info['name']), 'notice');
do_action('updraftplus_unzip_progress_restore_info', $file, $i, $size_written, $num_files);
$last_logged_bytes = $size_written;
$last_logged_index = $i;
$last_logged_time = time();
$last_saved_time = time();
}
// Because a lot can happen in 5 seconds, we update the job data more often
if (time() > $last_saved_time + 5) {
// N.B. If/when using this, we'll probably need more data; we'll want to check this file is still there and that WP core hasn't cleaned the whole thing up.
$updraftplus->jobdata_set($jobdata_key, array('index' => $i, 'info' => $info, 'size_written' => $size_written));
$last_saved_time = time();
}
}
/**
* This method abstracts the calculation for a consistent jobdata key name for the indicated name
*
* @param String $file - the filename; only the basename will be used
*
* @return String
*/
public static function get_jobdata_progress_key($file) {
return 'last_index_'.md5(basename($file));
}
/**
* Compatibility function (exists in WP 4.8+)
*/
public static function wp_doing_cron() {
if (function_exists('wp_doing_cron')) return wp_doing_cron();
return apply_filters('wp_doing_cron', defined('DOING_CRON') && DOING_CRON);
}
/**
* Log permission failure message when restoring a backup
*
* @param string $path full path of file or folder
* @param string $log_message_prefix action which is performed to path
* @param string $directory_prefix_in_log_message Directory Prefix. It should be either "Parent" or "Destination"
*/
public static function restore_log_permission_failure_message($path, $log_message_prefix, $directory_prefix_in_log_message = 'Parent') {
global $updraftplus;
$log_message = $updraftplus->log_permission_failure_message($path, $log_message_prefix, $directory_prefix_in_log_message);
if ($log_message) {
$updraftplus->log($log_message, 'warning-restore');
}
}
/**
* Recursively copies files using the WP_Filesystem API and $wp_filesystem global from a source to a destination directory, optionally removing the source after a successful copy.
*
* @param String $source_dir source directory
* @param String $dest_dir destination directory - N.B. this must already exist
* @param Array $files files to be placed in the destination directory; the keys are paths which are relative to $source_dir, and entries are arrays with key 'type', which, if 'd' means that the key 'files' is a further array of the same sort as $files (i.e. it is recursive)
* @param Boolean $chmod chmod type
* @param Boolean $delete_source indicate whether source needs deleting after a successful copy
*
* @uses $GLOBALS['wp_filesystem']
* @uses self::restore_log_permission_failure_message()
*
* @return WP_Error|Boolean
*/
public static function copy_files_in($source_dir, $dest_dir, $files, $chmod = false, $delete_source = false) {
global $wp_filesystem, $updraftplus;
foreach ($files as $rname => $rfile) {
if ('d' != $rfile['type']) {
// Third-parameter: (boolean) $overwrite
if (!$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, true)) {
self::restore_log_permission_failure_message($dest_dir, $source_dir.'/'.$rname.' -> '.$dest_dir.'/'.$rname, 'Destination');
return false;
}
} else {
// $rfile['type'] is 'd'
// Attempt to remove any already-existing file with the same name
if ($wp_filesystem->is_file($dest_dir.'/'.$rname)) @$wp_filesystem->delete($dest_dir.'/'.$rname, false, 'f');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- if fails, carry on
// No such directory yet: just move it
if ($wp_filesystem->exists($dest_dir.'/'.$rname) && !$wp_filesystem->is_dir($dest_dir.'/'.$rname) && !$wp_filesystem->move($source_dir.'/'.$rname, $dest_dir.'/'.$rname, false)) {
self::restore_log_permission_failure_message($dest_dir, 'Move '.$source_dir.'/'.$rname.' -> '.$dest_dir.'/'.$rname, 'Destination');
$updraftplus->log_e('Failed to move directory (check your file permissions and disk quota): %s', $source_dir.'/'.$rname." -> ".$dest_dir.'/'.$rname);
return false;
} elseif (!empty($rfile['files'])) {
if (!$wp_filesystem->exists($dest_dir.'/'.$rname)) $wp_filesystem->mkdir($dest_dir.'/'.$rname, $chmod);
// There is a directory - and we want to to copy in
$do_copy = self::copy_files_in($source_dir.'/'.$rname, $dest_dir.'/'.$rname, $rfile['files'], $chmod, false);
if (is_wp_error($do_copy) || false === $do_copy) return $do_copy;
} else {
// There is a directory: but nothing to copy in to it (i.e. $file['files'] is empty). Just remove the directory.
@$wp_filesystem->rmdir($source_dir.'/'.$rname);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
}
}
}
// We are meant to leave the working directory empty. Hence, need to rmdir() once a directory is empty. But not the root of it all in case of others/wpcore.
if ($delete_source || false !== strpos($source_dir, '/')) {
if (!$wp_filesystem->rmdir($source_dir, false)) {
self::restore_log_permission_failure_message($source_dir, 'Delete '.$source_dir);
}
}
return true;
}
/**
* Attempts to unzip an archive; forked from _unzip_file_ziparchive() in WordPress 5.1-alpha-44182, and modified to use the UD zip classes.
*
* Assumes that WP_Filesystem() has already been called and set up.
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param String $file - full path and filename of ZIP archive.
* @param String $to - full path on the filesystem to extract archive to.
* @param Array $needed_dirs - a partial list of required folders needed to be created.
* @param String $method - either 'ziparchive' or 'pclzip'.
* @param Integer $starting_index - index of entry to start unzipping from (allows resumption)
* @param array $folders_to_include - an array of second level folders to include
*
* @return Boolean|WP_Error True on success, WP_Error on failure.
*/
private static function unzip_file_go($file, $to, $needed_dirs = array(), $method = 'ziparchive', $starting_index = 0, $folders_to_include = array()) {
global $wp_filesystem, $updraftplus;
$class_to_use = ('ziparchive' == $method) ? 'UpdraftPlus_ZipArchive' : 'UpdraftPlus_PclZip';
if (!class_exists($class_to_use)) updraft_try_include_file('includes/class-zip.php', 'require_once');
$updraftplus->log('Unzipping '.basename($file).' to '.$to.' using '.$class_to_use.', starting index '.$starting_index);
$z = new $class_to_use;
$flags = (version_compare(PHP_VERSION, '5.2.12', '>') && defined('ZIPARCHIVE::CHECKCONS')) ? ZIPARCHIVE::CHECKCONS : 4;
// This is just for crazy people with mbstring.func_overload enabled (deprecated from PHP 7.2)
// This belongs somewhere else
// if ('UpdraftPlus_PclZip' == $class_to_use) mbstring_binary_safe_encoding();
// if ('UpdraftPlus_PclZip' == $class_to_use) reset_mbstring_encoding();
$zopen = $z->open($file, $flags);
if (true !== $zopen) {
return new WP_Error('incompatible_archive', __('Incompatible Archive.'), array($method.'_error' => $z->last_error));
}
$uncompressed_size = 0;
$num_files = $z->numFiles;
if (false === $num_files) return new WP_Error('incompatible_archive', __('Incompatible Archive.'), array($method.'_error' => $z->last_error));
for ($i = $starting_index; $i < $num_files; $i++) {
if (!$info = $z->statIndex($i)) {
return new WP_Error('stat_failed_'.$method, __('Could not retrieve file from archive.').' ('.$z->last_error.')');
}
// Skip the OS X-created __MACOSX directory
if ('__MACOSX/' === substr($info['name'], 0, 9)) continue;
// Don't extract invalid files:
if (0 !== validate_file($info['name'])) continue;
if (!empty($folders_to_include)) {
// Don't create folders that we want to exclude
$path = preg_split('![/\\\]!', untrailingslashit($info['name']));
if (isset($path[1]) && !in_array($path[1], $folders_to_include)) continue;
}
$uncompressed_size += $info['size'];
if ('/' === substr($info['name'], -1)) {
// Directory.
$needed_dirs[] = $to . untrailingslashit($info['name']);
} elseif ('.' !== ($dirname = dirname($info['name']))) {
// Path to a file.
$needed_dirs[] = $to . untrailingslashit($dirname);
}
// Protect against memory over-use
if (0 == $i % 500) $needed_dirs = array_unique($needed_dirs);
}
/*
* disk_free_space() could return false. Assume that any falsey value is an error.
* A disk that has zero free bytes has bigger problems.
* Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
*/
if (self::wp_doing_cron()) {
$available_space = function_exists('disk_free_space') ? @disk_free_space(WP_CONTENT_DIR) : false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Call is speculative
if ($available_space && ($uncompressed_size * 2.1) > $available_space) {
return new WP_Error('disk_full_unzip_file', __('Could not copy files.', 'updraftplus').' '.__('You may have run out of disk space.'), compact('uncompressed_size', 'available_space'));
}
}
$needed_dirs = array_unique($needed_dirs);
foreach ($needed_dirs as $dir) {
// Check the parent folders of the folders all exist within the creation array.
if (untrailingslashit($to) == $dir) {
// Skip over the working directory, We know this exists (or will exist)
continue;
}
// If the directory is not within the working directory then skip it
if (false === strpos($dir, $to)) continue;
$parent_folder = dirname($dir);
while (!empty($parent_folder) && untrailingslashit($to) != $parent_folder && !in_array($parent_folder, $needed_dirs)) {
$needed_dirs[] = $parent_folder;
$parent_folder = dirname($parent_folder);
}
}
asort($needed_dirs);
// Create those directories if need be:
foreach ($needed_dirs as $_dir) {
// Only check to see if the Dir exists upon creation failure. Less I/O this way.
if (!$wp_filesystem->mkdir($_dir, FS_CHMOD_DIR) && !$wp_filesystem->is_dir($_dir)) {
return new WP_Error('mkdir_failed_'.$method, __('Could not create directory.'), substr($_dir, strlen($to)));
}
}
unset($needed_dirs);
$size_written = 0;
$content_cache = array();
$content_cache_highest = -1;
for ($i = $starting_index; $i < $num_files; $i++) {
if (!$info = $z->statIndex($i)) {
return new WP_Error('stat_failed_'.$method, __('Could not retrieve file from archive.'));
}
// directory
if ('/' == substr($info['name'], -1)) continue;
// Don't extract the OS X-created __MACOSX
if ('__MACOSX/' === substr($info['name'], 0, 9)) continue;
// Don't extract invalid files:
if (0 !== validate_file($info['name'])) continue;
if (!empty($folders_to_include)) {
// Don't extract folders that we want to exclude
$path = preg_split('![/\\\]!', untrailingslashit($info['name']));
if (isset($path[1]) && !in_array($path[1], $folders_to_include)) continue;
}
// N.B. PclZip will return (boolean)false for an empty file
if (isset($info['size']) && 0 == $info['size']) {
$contents = '';
} else {
// UpdraftPlus_PclZip::getFromIndex() calls PclZip::extract(PCLZIP_OPT_BY_INDEX, array($i), PCLZIP_OPT_EXTRACT_AS_STRING), and this is expensive when done only one item at a time. We try to cache in chunks for good performance as well as being able to resume.
if ($i > $content_cache_highest && 'UpdraftPlus_PclZip' == $class_to_use) {
$memory_usage = memory_get_usage(false);
$total_memory = $updraftplus->memory_check_current();
if ($memory_usage > 0 && $total_memory > 0) {
$memory_free = $total_memory*1048576 - $memory_usage;
} else {
// A sane default. Anything is ultimately better than WP's default of just unzipping everything into memory.
$memory_free = 50*1048576;
}
$use_memory = max(10485760, $memory_free - 10485760);
$total_byte_count = 0;
$content_cache = array();
$cache_indexes = array();
$cache_index = $i;
while ($cache_index < $num_files && $total_byte_count < $use_memory) {
if (false !== ($cinfo = $z->statIndex($cache_index)) && isset($cinfo['size']) && '/' != substr($cinfo['name'], -1) && '__MACOSX/' !== substr($cinfo['name'], 0, 9) && 0 === validate_file($cinfo['name'])) {
$total_byte_count += $cinfo['size'];
if ($total_byte_count < $use_memory) {
$cache_indexes[] = $cache_index;
$content_cache_highest = $cache_index;
}
}
$cache_index++;
}
if (!empty($cache_indexes)) {
$content_cache = $z->updraftplus_getFromIndexBulk($cache_indexes);
}
}
$contents = isset($content_cache[$i]) ? $content_cache[$i] : $z->getFromIndex($i);
}
if (false === $contents && ('pclzip' !== $method || 0 !== $info['size'])) {
return new WP_Error('extract_failed_'.$method, __('Could not extract file from archive.').' '.$z->last_error, json_encode($info));
}
if (!$wp_filesystem->put_contents($to . $info['name'], $contents, FS_CHMOD_FILE)) {
return new WP_Error('copy_failed_'.$method, __('Could not copy file.'), $info['name']);
}
if (!empty($info['size'])) $size_written += $info['size'];
do_action('updraftplus_unzip_file_unzipped', $file, $i, $info, $size_written, $num_files);
}
$z->close();
return true;
}
}
require_once(dirname(__FILE__) . '/wfAPI.php');
require_once(dirname(__FILE__) . '/wfBinaryList.php');
class wordfenceURLHoover {
private $debug = false;
public $errorMsg = false;
private $hostsToAdd = false;
private $table = '';
private $apiKey = false;
private $wordpressVersion = false;
private $useDB = true;
private $hostKeys = array();
private $hostList = array();
public $currentHooverID = false;
private $_foundSome = false;
private $_excludedHosts = array();
private $api = false;
private $db = false;
public static function standardExcludedHosts() {
static $standardExcludedHosts = null;
if ($standardExcludedHosts !== null) {
return $standardExcludedHosts;
}
global $wpdb;
$excludedHosts = array();
if (is_multisite()) {
$blogIDs = $wpdb->get_col("SELECT blog_id FROM {$wpdb->blogs}"); //Can't use wp_get_sites or get_sites because they return empty at 10k sites
foreach ($blogIDs as $id) {
$homeURL = get_home_url($id);
$host = parse_url($homeURL, PHP_URL_HOST);
if ($host) {
$excludedHosts[$host] = 1;
}
$siteURL = get_site_url($id);
$host = parse_url($siteURL, PHP_URL_HOST);
if ($host) {
$excludedHosts[$host] = 1;
}
}
}
else {
$homeURL = wfUtils::wpHomeURL();
$host = parse_url($homeURL, PHP_URL_HOST);
if ($host) {
$excludedHosts[$host] = 1;
}
$siteURL = wfUtils::wpSiteURL();
$host = parse_url($siteURL, PHP_URL_HOST);
if ($host) {
$excludedHosts[$host] = 1;
}
}
$standardExcludedHosts = array_keys($excludedHosts);
return $standardExcludedHosts;
}
public function __sleep() {
$this->writeHosts();
return array('debug', 'errorMsg', 'apiKey', 'wordpressVersion');
}
public function __wakeup() {
$this->hostsToAdd = array();
$this->api = new wfAPI($this->apiKey, $this->wordpressVersion);
$this->db = new wfDB();
global $wpdb;
$this->table = isset($wpdb) ? wfDB::networkTable('wfHoover') : 'wp_wfHoover';
}
public function __construct($apiKey, $wordpressVersion, $continuation = false) {
$this->hostsToAdd = array();
$this->apiKey = $apiKey;
$this->wordpressVersion = $wordpressVersion;
$this->api = new wfAPI($apiKey, $wordpressVersion);
$this->db = new wfDB();
global $wpdb;
$this->table = isset($wpdb) ? wfDB::networkTable('wfHoover') : 'wp_wfHoover';
if (!$continuation) {
$this->cleanup();
}
}
public function cleanup() {
$this->db->truncate($this->table);
}
public function hoover($id, $data, $excludedHosts = array()) {
$this->currentHooverID = $id;
$this->_foundSome = 0;
$this->_excludedHosts = $excludedHosts;
@preg_replace_callback('_((?:(?://)(?:\S+(?::\S*)?@)?(?:(?:(?:[a-z\xa1-\xff0-9.-]+)(?:\.(?:(?:xn--[a-z\xa1-\xff0-9-]+)|[a-z\xa1-\xff]{2,}))))(?::\d{2,5})?)(?:/[a-z0-9\-\_\.~\!\*\(\);\:@&\=\+\$,\?#\[\]%]*)*)_iS', array($this, 'captureURL'), $data);
$this->writeHosts();
return $this->_foundSome;
}
private function dbg($msg) {
if ($this->debug) { wordfence::status(4, 'info', $msg); }
}
public function captureURL($matches) {
$id = $this->currentHooverID;
$url = 'http:' . $matches[0];
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return;
}
$components = parse_url($url);
if (preg_match('/\.(xn--(?:[a-z0-9-]*)[a-z0-9]+|[a-z\xa1-\xff0-9]{2,})$/i', $components['host'], $tld)) {
$tld = strtolower($tld[1]);
if (strpos(wfConfig::get('tldlist', ''), '|' . $tld . '|') === false) {
return;
}
}
else {
return;
}
foreach ($this->_excludedHosts as $h) {
if (strcasecmp($h, $components['host']) === 0) {
return;
}
}
$this->_foundSome++;
$host = (isset($components['host']) ? $components['host'] : '');
$hashes = $this->_generateHashes($url);
foreach ($hashes as $h) {
$this->_queueHost($id, $host, wfUtils::substr($h, 0, 4));
}
if (count($this->hostsToAdd) > 1000){ $this->writeHosts(); }
}
/**
* Queues the host for writing to the DB. Deduplication is performed here to minimize the row count on sites with
* large numbers of found URLs, and the exclusions lists have already been processed by the time it reaches this
* point.
*
* @param string $owner
* @param string $host
* @param string $key
*/
private function _queueHost($owner, $host, $key) {
$indexKey = md5($owner . '|' . $host, true);
if (array_key_exists($indexKey, $this->hostsToAdd)) {
return;
}
$this->hostsToAdd[$indexKey] = array('owner' => $owner, 'host' => $host, 'hostKey' => $key);
}
private function writeHosts() {
if (count($this->hostsToAdd) < 1) { return; }
if ($this->useDB) {
$sql = "INSERT INTO " . $this->table . " (owner, host, path, hostKey) VALUES ";
while ($elem = array_shift($this->hostsToAdd)) {
//This may be an issue for hyperDB or other abstraction layers, but leaving it for now.
$sql .= sprintf("('%s', '%s', '', '%s'),",
$this->db->realEscape($elem['owner']),
$this->db->realEscape($elem['host']),
$this->db->realEscape($elem['hostKey'])
);
}
$sql = rtrim($sql, ',');
$this->db->queryWrite($sql);
}
else {
while ($elem = array_shift($this->hostsToAdd)) {
$keys = str_split($elem['hostKey'], 4);
foreach ($keys as $k) {
$this->hostKeys[] = $k;
}
$this->hostList[] = array(
'owner' => $elem['owner'],
'host' => $elem['host'],
'hostKey' => $elem['hostKey']
);
}
}
}
public function getBaddies() {
wordfence::status(4, 'info', __("Gathering host keys.", 'wordfence'));
$allHostKeys = '';
if ($this->useDB) {
global $wpdb;
$dbh = $wpdb->dbh;
$useMySQLi = wfUtils::useMySQLi();
if ($useMySQLi) { //If direct-access MySQLi is available, we use it to minimize the memory footprint instead of letting it fetch everything into an array first
wordfence::status(4, 'info', __("Using MySQLi directly.", 'wordfence'));
$result = $dbh->query("SELECT DISTINCT hostKey FROM {$this->table} ORDER BY hostKey ASC LIMIT 100000"); /* We limit to 100,000 prefixes since more than that cannot be reliably checked within the default max_execution_time */
if (!is_object($result)) {
$this->errorMsg = "Unable to query database";
$this->dbg($this->errorMsg);
return false;
}
while ($row = $result->fetch_assoc()) {
$allHostKeys .= $row['hostKey'];
}
}
else {
$q1 = $this->db->querySelect("SELECT DISTINCT hostKey FROM {$this->table} ORDER BY hostKey ASC LIMIT 100000"); /* We limit to 100,000 prefixes since more than that cannot be reliably checked within the default max_execution_time */
foreach ($q1 as $hRec) {
$allHostKeys .= $hRec['hostKey'];
}
}
}
else {
$allHostKeys = implode('', array_values(array_unique($this->hostKeys)));
}
/**
* Check hash prefixes first. Each one is a 4-byte binary prefix of a SHA-256 hash of the URL. The response will
* be a binary list of 4-byte indices; The full URL for each index should be sent in the secondary query to
* find the true good/bad status.
*/
$allCount = wfUtils::strlen($allHostKeys) / 4;
if ($allCount > 0) {
if ($this->debug) {
$this->dbg("Checking {$allCount} hostkeys");
for ($i = 0; $i < $allCount; $i++) {
$key = wfUtils::substr($allHostKeys, $i * 4, 4);
$this->dbg("Checking hostkey: " . bin2hex($key));
}
}
wordfence::status(2, 'info', sprintf(/* translators: Number of domains. */ __("Checking %d host keys against Wordfence scanning servers.", 'wordfence'), $allCount));
$resp = $this->api->binCall('check_host_keys', $allHostKeys);
wordfence::status(2, 'info', __("Done host key check.", 'wordfence'));
$this->dbg("Done host key check");
$badHostKeys = '';
if ($resp['code'] >= 200 && $resp['code'] <= 299) {
$this->dbg("Host key response: " . bin2hex($resp['data']));
$dataLen = wfUtils::strlen($resp['data']);
if ($dataLen > 0 && $dataLen % 2 == 0) {
$this->dbg("Checking response indexes");
for ($i = 0; $i < $dataLen; $i += 2) {
$idx = wfUtils::array_first(unpack('n', wfUtils::substr($resp['data'], $i, 2)));
$this->dbg("Checking index {$idx}");
if ($idx < $allCount) {
$prefix = wfUtils::substr($allHostKeys, $idx * 4, 4);
$badHostKeys .= $prefix;
$this->dbg("Got bad hostkey for record: " . bin2hex($prefix));
}
else {
$this->dbg("Bad allHostKeys index: {$idx}");
$this->errorMsg = "Bad allHostKeys index: {$idx}";
return false;
}
}
}
else if ($dataLen > 0) {
$this->errorMsg = "Invalid data length received from Wordfence server: " . $dataLen;
$this->dbg($this->errorMsg);
return false;
}
}
else {
$this->errorMsg = "Wordfence server responded with an error. HTTP code " . $resp['code'] . " and data: " . $resp['data'];
return false;
}
$badCount = wfUtils::strlen($badHostKeys) / 4;
if ($badCount > 0) {
$urlsToCheck = array();
$totalURLs = 0;
//Reconcile flagged prefixes with their corresponding URLs
for ($i = 0; $i < $badCount; $i++) {
$prefix = wfUtils::substr($badHostKeys, $i * 4, 4);
if ($this->useDB) {
/**
* Putting a 10000 limit in here for sites that have a huge number of items with the same URL
* that repeats. This is an edge case. But if the URLs are malicious then presumably the admin
* will fix the malicious URLs and on subsequent scans the items (owners) that are above the
* 10000 limit will appear.
*/
$q1 = $this->db->querySelect("SELECT DISTINCT owner, host FROM {$this->table} WHERE hostKey = %s LIMIT 10000", $prefix);
foreach ($q1 as $rec) {
$url = 'http://' . $rec['host'];
if (!isset($urlsToCheck[$rec['owner']])) {
$urlsToCheck[$rec['owner']] = array();
}
if (!in_array($url, $urlsToCheck[$rec['owner']])) {
$urlsToCheck[$rec['owner']][] = $url;
$totalURLs++;
}
}
}
else {
foreach ($this->hostList as $rec) {
$pos = wfUtils::strpos($rec['hostKey'], $prefix);
if ($pos !== false && $pos % 4 == 0) {
$url = 'http://' . $rec['host'];
if (!isset($urlsToCheck[$rec['owner']])) {
$urlsToCheck[$rec['owner']] = array();
}
if (!in_array($url, $urlsToCheck[$rec['owner']])) {
$urlsToCheck[$rec['owner']][] = $url;
$totalURLs++;
}
}
}
}
if ($totalURLs > 10000) { break; }
}
if (count($urlsToCheck) > 0) {
wordfence::status(2, 'info', sprintf(
/* translators: 1. Number of URLs. 2. Number of files. */
__('Checking %1$d URLs from %2$d sources.', 'wordfence'),
$totalURLs,
sizeof($urlsToCheck)
));
$badURLs = $this->api->call('check_bad_urls', array(), array('toCheck' => json_encode($urlsToCheck)));
wordfence::status(2, 'info', __("Done URL check.", 'wordfence'));
$this->dbg("Done URL check");
if (is_array($badURLs) && count($badURLs) > 0) {
$finalResults = array();
foreach ($badURLs as $file => $badSiteList) {
if (!isset($finalResults[$file])) {
$finalResults[$file] = array();
}
foreach ($badSiteList as $badSite) {
$finalResults[$file][] = array(
'URL' => $badSite[0],
'badList' => $badSite[1]
);
}
}
$this->dbg("Confirmed " . count($badURLs) . " bad URLs");
return $finalResults;
}
}
}
}
return array();
}
/**
* Computes the canonical URL for $url and generates the hash variants that we'll check the safe browsing list for.
*
* @param string $url
* @return string[]
*/
protected function _generateHashes($url) {
$canonicalURL = $this->_canonicalizeURL($url);
//Extract the scheme
$scheme = 'http';
if (preg_match('~^([a-z]+[a-z0-9+\.\-]*)://(.*)$~i', $canonicalURL, $matches)) {
$scheme = strtolower($matches[1]);
$canonicalURL = $matches[2];
}
//Separate URL and query string
$query = '';
if (preg_match('/^([^?]+)(\??.*)/', $canonicalURL, $matches)) {
$canonicalURL = $matches[1];
$query = $matches[2];
}
//Separate host and path
$path = '';
preg_match('~^(.*?)(?:(/.*)|$)~', $canonicalURL, $matches);
$host = $matches[1];
if (isset($matches[2])) {
$path = $matches[2];
}
//Clean host
$host = $this->_normalizeHost($host);
//Generate hosts list
$hosts = array();
if (filter_var(trim($host, '[]'), FILTER_VALIDATE_IP)) {
$hosts[] = $host;
}
else {
$hostComponents = explode('.', $host);
$numComponents = count($hostComponents) - 7;
if ($numComponents < 1) {
$numComponents = 1;
}
$hosts[] = $host;
for ($i = $numComponents; $i < count($hostComponents) - 1; $i++) {
$hosts[] = implode('.', array_slice($hostComponents, $i));
}
}
//Generate hashes
$hashes = array();
foreach ($hosts as $h) {
if (($hash = $this->_shouldCheckHost($h)) !== false) {
$hashes[$h] = $hash; //WFSB preferred hash -- it uses hashes without any path
}
//Future hash needs may be added here
}
return $hashes;
}
protected function _canonicalizeURL($url) { //Based on https://developers.google.com/safe-browsing/v4/urls-hashing#canonicalization and Google's reference implementation https://github.com/google/safebrowsing/blob/master/urls.go
//Strip fragment
$url = $this->_array_first(explode('#', $url));
//Trim space
$url = trim($url);
//Remove tabs, CR, LF
$url = preg_replace('/[\t\n\r]/', '', $url);
//Normalize escapes
$url = $this->_normalizeEscape($url);
if ($url === false) { return false; }
//Extract the scheme
$scheme = 'http';
if (preg_match('~^([a-z]+[a-z0-9+\.\-]*)://(.*)$~i', $url, $matches)) {
$scheme = strtolower($matches[1]);
$url = $matches[2];
}
//Separate URL and query string
$query = '';
if (preg_match('/^([^?]+)(\??.*)/', $url, $matches)) {
$url = $matches[1];
$query = $matches[2];
}
$endsWithSlash = substr($url, -1) == '/';
//Separate host and path
$path = '';
preg_match('~^(.*?)(?:(/.*)|$)~', $url, $matches);
$host = $matches[1];
if (isset($matches[2])) {
$path = $matches[2];
}
//Clean host
$host = $this->_normalizeHost($host);
if ($host === false) { return false; }
//Clean path
$path = preg_replace('~//+~', '/', $path); //Multiple slashes -> single slash
$path = preg_replace('~(?:^|/)\.(?:$|/)~', '/', $path); //. path components removed
while (preg_match('~/(?!\.\./)[^/]+/\.\.(?:$|/)~', $path)) { //Resolve ..
$path = preg_replace('~/(?!\.\./)[^/]+/\.\.(?:$|/)~', '/', $path, 1);
}
$path = preg_replace('~(?:^|/)\.\.(?:$|/)~', '/', $path); //Eliminate .. at the beginning
$path = trim($path, '.');
$path = preg_replace('/\.\.+/', '.', $path);
if ($path == '.' || $path == '') {
$path = '/';
}
else if ($endsWithSlash && substr($path, -1) != '/') {
$path .= '/';
}
return $scheme . '://' . $host . $path . $query;
}
protected function _normalizeEscape($url) {
$maxDepth = 1024;
$i = 0;
while (preg_match('/%([0-9a-f]{2})/i', $url)) {
$url = preg_replace_callback('/%([0-9a-f]{2})/i', array($this, '_hex2binCallback'), $url);
$i++;
if ($i > $maxDepth) {
return false;
}
}
return preg_replace_callback('/[\x00-\x20\x7f-\xff#%]/', array($this, '_bin2hexCallback'), $url);
}
protected function _hex2binCallback($matches) {
return wfUtils::hex2bin($matches[1]);
}
protected function _bin2hexCallback($matches) {
return '%' . bin2hex($matches[0]);
}
protected function _normalizeHost($host) {
//Strip username:password
$host = $this->_array_last(explode('@', $host));
//IPv6 literal
if (substr($host, 0, 1) == '[') {
if (strpos($host, ']') === false) { //No closing bracket
return false;
}
}
//Strip port
$host = preg_replace('/:\d+$/', '', $host);
//Unicode to IDNA
$u = rawurldecode($host);
if (preg_match('/[\x81-\xff]/', $u)) { //0x80 is technically Unicode, but the GSB canonicalization doesn't consider it one
if (function_exists('idn_to_ascii')) { //Some PHP versions don't have this and we don't have a polyfill
$host = idn_to_ascii($u);
}
}
//Remove extra dots
$host = trim($host, '.');
$host = preg_replace('/\.\.+/', '.', $host);
//Canonicalize IP addresses
if ($iphost = $this->_parseIP($host)) {
return $iphost;
}
return strtolower($host);
}
protected function _parseIP($host) {
// The Windows resolver allows a 4-part dotted decimal IP address to have a
// space followed by any old rubbish, so long as the total length of the
// string doesn't get above 15 characters. So, "10.192.95.89 xy" is
// resolved to 10.192.95.89. If the string length is greater than 15
// characters, e.g. "10.192.95.89 xy.wildcard.example.com", it will be
// resolved through DNS.
if (strlen($host) <= 15) {
$host = $this->_array_first(explode(' ', $host));
}
if (!preg_match('/^((?:0x[0-9a-f]+|[0-9\.])+)$/i', $host)) {
return false;
}
$parts = explode('.', $host);
if (count($parts) > 4) {
return false;
}
$strings = array();
foreach ($parts as $i => $p) {
if ($i == count($parts) - 1) {
$strings[] = $this->_canonicalNum($p, 5 - count($parts));
}
else {
$strings[] = $this->_canonicalNum($p, 1);
}
if ($strings[$i] == '') {
return '';
}
}
return implode('.', $strings);
}
protected function _canonicalNum($part, $n) {
if ($n <= 0 || $n > 4) {
return '';
}
if (preg_match('/^0x(\d+)$/i', $part, $matches)) { //hex
$part = hexdec($matches[1]);
}
else if (preg_match('/^0(\d+)$/i', $part, $matches)) { //octal
$part = octdec($matches[1]);
}
else {
$part = (int) $part;
}
$strings = array_fill(0, $n, '');
for ($i = $n - 1; $i >= 0; $i--) {
$strings[$i] = (string) ($part & 0xff);
$part = $part >> 8;
}
return implode('.', $strings);
}
/**
* Checks whether the host should be included or not by querying the skip list. If yes, returns the binary sha256
* hash of it.
*
* @param string $host
* @return false|string
*/
protected function _shouldCheckHost($host) {
static $skipList = null;
if ($skipList == null) {
$skipList = new wfBinaryList(base64_decode(wfConfig::get('wfsbskip', '')));
}
$hash = hash('sha256', $host, true);
return $skipList->contains($hash) === false ? $hash : false;
}
protected function _array_first($array) {
if (empty($array)) {
return null;
}
return $array[0];
}
protected function _array_last($array) {
if (empty($array)) {
return null;
}
return $array[count($array) - 1];
}
}
class wfCommonPasswords {
const BASE_LIST = [
"password",
"1234567890",
"123456789",
"12345678",
"1234567",
"123456",
"12345",
"1234",
"123",
"12",
"1"
];
const EXTENDED_LIST = [
"Password",
"passwd",
"admin",
"administrator",
"super",
"superuser",
"supervisor",
"root",
"manager",
"mgr",
"abc123",
"qwerty",
"asdf",
"zxcv",
"9876543210",
"876543210",
"76543210",
"6543210",
"543210",
"43210",
"3210",
"210",
"10",
"0",
"1",
"11",
"111",
"1111",
"11111",
"2",
"22",
"222",
"2222",
"22222",
"3",
"33",
"333",
"3333",
"33333",
"4",
"44",
"444",
"4444",
"44444",
"5",
"55",
"555",
"5555",
"55555",
"6",
"66",
"666",
"6666",
"66666",
"7",
"77",
"777",
"7777",
"77777",
"8",
"88",
"888",
"8888",
"88888",
"9",
"99",
"999",
"9999",
"99999",
"0",
"00",
"000",
"0000",
"00000"
];
public static function getList($extended = false) {
$list = self::BASE_LIST;
if ($extended)
$list = array_merge($list, self::EXTENDED_LIST);
return $list;
}
public static function addToKeyedList(&$list, $extended = false) {
foreach (self::getList($extended) as $password) {
$list[$password] = true;
}
}
}
/**
* Contact Form
*
* Displays a customizable contact form
*/
if ( ! defined( 'ABSPATH' ) ) { exit; } // Exit if accessed directly
if ( !class_exists( 'avia_sc_contact' ) )
{
class avia_sc_contact extends aviaShortcodeTemplate
{
/**
* Create the config array for the shortcode button
*/
function shortcode_insert_button()
{
$this->config['self_closing'] = 'no';
$this->config['name'] = __('Contact Form', 'avia_framework' );
$this->config['tab'] = __('Content Elements', 'avia_framework' );
$this->config['icon'] = AviaBuilder::$path['imagesURL']."sc-contact.png";
$this->config['order'] = 43;
$this->config['target'] = 'avia-target-insert';
$this->config['shortcode'] = 'av_contact';
$this->config['shortcode_nested'] = array('av_contact_field');
$this->config['tooltip'] = __('Creates a customizable contact form', 'avia_framework' );
$this->config['preview'] = "large";
$this->config['disabling_allowed'] = true;
$this->config['id_name'] = 'id';
$this->config['id_show'] = 'yes';
$this->config['aria_label'] = 'yes';
$this->config['alb_desc_id'] = 'alb_description';
}
function extra_assets()
{
//load css
wp_enqueue_style( 'avia-module-contact' , AviaBuilder::$path['pluginUrlRoot'].'avia-shortcodes/contact/contact.css' , array('avia-layout'), false );
//load js
wp_enqueue_script( 'avia-module-contact' , AviaBuilder::$path['pluginUrlRoot'].'avia-shortcodes/contact/contact.js' , array('avia-shortcodes'), false, true );
}
/**
* Popup Elements
*
* If this function is defined in a child class the element automatically gets an edit button, that, when pressed
* opens a modal window that allows to edit the element properties
*
* @return void
*/
function popup_elements()
{
$link = '' . __( 'activated here', 'avia_framework' ) . '';
$captcha_desc = __( 'Do you want to display a Captcha field at the end of the form so users must prove they are human?', 'avia_framework' ) . '';
$captcha_desc .= __( 'Either by solving a simply mathematical question or by Google reCaptcha, that needs to be', 'avia_framework' ) . ' ' . $link . '. ';
$captcha_desc .= __( 'In case Google reCAPTCHA is deactivated (maybe later) in theme options, Enfold captcha will be used, if you selected to use V2 then V2 will be used for this contact form (even if you selected V3 in theme options). If you selected V3 here and the score fails or you did not selected V3 in theme options then V2 will be used to check if user is a human.', 'avia_framework' );
$captcha_desc .= '';
$captcha_desc .= __( '(It is recommended to only activate this if you receive spam from your contact form, since an invisible spam protection is also implemented that should filter most spam messages by robots anyway)', 'avia_framework' );
$default_from = parse_url( home_url() );
$default_from = ( ! empty( $default_from['host'] ) ) ? "no-reply@{$default_from['host']}" : 'no-reply@wp-message.com';
$this->elements = apply_filters( 'avf_sc_contact_popup_elements', array(
array(
"type" => "tab_container", 'nodescription' => true
),
array(
"type" => "tab",
"name" => __("Form" , 'avia_framework'),
'nodescription' => true
),
array(
"name" => __("Your email address", 'avia_framework' ),
"desc" => __("Enter one or more Email addresses (separated by comma) where mails should be delivered to.", 'avia_framework' ) ." (".__("Default:", 'avia_framework' ) ." ". get_option('admin_email').")",
"id" => "email",
'container_class' =>"avia-element-fullwidth",
"std" => get_option('admin_email'),
"type" => "input"),
array(
'name' => __( 'Your from address', 'avia_framework' ),
'desc' => sprintf( __( 'Enter your from address for the form - if left blank it will default to user email or %s', 'avia_framework' ), $default_from ),
'id' => 'from_email',
'std' => '',
'type' => 'input'
),
array(
"name" => __("Form Title", 'avia_framework' ),
"desc" => __("Enter a form title that is displayed above the form", 'avia_framework' ),
"id" => "title",
"std" => __("Send us mail", 'avia_framework' ),
"type" => "input"),
array(
'type' => 'template',
'template_id' => 'heading_tag',
'theme_default' => 'h3',
'context' => __CLASS__
),
array(
"name" => __("Add/Edit Contact Form Elements", 'avia_framework' ),
"desc" => __("Here you can add, remove and edit the form Elements of your contact form.", 'avia_framework' )." ".
__("Available form elements are: single line Input elements, Textareas, Checkboxes and Select-Dropdown menus.", 'avia_framework' )."
".
__("It is recommended to not delete the 'E-Mail' field if you want to use an auto responder.", 'avia_framework' ),
"type" => "modal_group",
"id" => "content",
"modal_title" => __("Edit Form Element", 'avia_framework' ),
"std" => array(
array('label'=>__('Name', 'avia_framework' ), 'type'=>'text', 'check'=>'is_empty'),
array('label'=>__('E-Mail', 'avia_framework' ), 'type'=>'text', 'check'=>'is_email'),
array('label'=>__('Subject', 'avia_framework' ), 'type'=>'text', 'check'=>'is_empty'),
array('label'=>__('Message', 'avia_framework' ), 'type'=>'textarea', 'check'=>'is_empty'),
),
'subelements' => array(
array(
"name" => __("Form Element Label", 'avia_framework' ),
"desc" => "",
"id" => "label",
"std" => "",
"type" => "input"),
array(
"name" => __("Form Element Type", 'avia_framework' ),
"desc" => "",
"id" => "type",
"type" => "select",
"std" => "text",
"no_first"=>true,
"subtype" => array( __('Form Element: Text Input', 'avia_framework' ) =>'text',
__('Form Element: Text Area', 'avia_framework' ) =>'textarea',
__('Form Element: Select Element', 'avia_framework' ) =>'select',
__('Form Element: Checkbox', 'avia_framework' ) =>'checkbox',
__('Form Element: Datepicker', 'avia_framework' ) =>'datepicker',
__('Custom HTML: Add a Description', 'avia_framework' ) =>'html',
)),
array(
"name" => __("Form Element Options", 'avia_framework' ) ,
"desc" => __("Enter any number of options that the visitor can choose from. Separate these Options with a comma.", 'avia_framework' ) ." ".
__("Example: Option 1, Option 2, Option 3", 'avia_framework' ).""." ".
__("Note: If you want to use a comma in the option text you have to write 2 comma.", 'avia_framework' )."" ,
"id" => "options",
"required" => array('type','equals','select'),
"std" => "",
"type" => "input"),
array(
"name" => __("Multiple answers", 'avia_framework' ),
"desc" => __("Check if you want to enable multiple answers", 'avia_framework' ) ,
"id" => "multi_select",
"required" => array('type','equals','select'),
"std" => "",
"type" => "checkbox"),
array(
"name" => __("Preselect checkbox", 'avia_framework' ),
"desc" => __("Check if you want to preselect the checkbox", 'avia_framework' ) ,
"id" => "av_contact_preselect",
"required" => array('type','equals','checkbox'),
"std" => "",
"type" => "checkbox"),
array(
"name" => __("Add Description", 'avia_framework' ) ,
"id" => "content",
"required" => array('type','equals','html'),
"std" => "",
"type" => "tiny_mce"),
array(
"name" => __("Form Element Validation", 'avia_framework' ),
"desc" => "When selecting "Valid E-Mail address with special characters" keep in mind, that not all E-Mail systems support this feature properly.",
"id" => "check",
"type" => "select",
"std" => "",
"no_first"=>true,
"required" => array('type','not','html'),
"subtype" => array( __('No Validation', 'avia_framework' ) =>'',
__('Is not empty', 'avia_framework' ) =>'is_empty',
__('Valid E-Mail address', 'avia_framework' ) =>'is_email',
__('Valid E-Mail address with special characters', 'avia_framework' ) =>'is_ext_email',
__('Valid Phone Number', 'avia_framework' ) =>'is_phone',
__('Valid Number', 'avia_framework' ) =>'is_number')),
array(
"name" => __("Form Element Width", 'avia_framework' ),
"desc" => __("Change the width of your elements and let them appear beside each other instead of underneath", 'avia_framework' ) ,
"id" => "width",
"type" => "select",
"std" => "",
"no_first"=>true,
"required" => array('type','not','html'),
"subtype" => array( "Fullwidth" =>'', "1/2" =>'element_half', "1/3" =>'element_third' , "2/3" =>'element_two_third', "1/4" => 'element_fourth', "3/4" => 'element_three_fourth')),
)
),
array(
"name" => __("Submit Button Label", 'avia_framework' ),
"desc" => __("Enter the submit buttons label text here", 'avia_framework' ),
"id" => "button",
"std" => __("Submit", 'avia_framework' ),
"type" => "input"),
array(
"name" => __("What should happen once the form gets sent?", 'avia_framework' ),
"desc" => "",
"id" => "on_send",
"type" => "select",
"std" => "",
"no_first"=>true,
"subtype" => array( __('Display a short message on the same page', 'avia_framework' ) =>'',
__('Redirect the user to another page', 'avia_framework' ) =>'redirect',
)),
array(
"name" => __("Message Sent label", 'avia_framework' ),
"desc" => __("What should be displayed once the message is sent?", 'avia_framework' ),
"id" => "sent",
"required" => array('on_send','not','redirect'),
"std" => __("Your message has been sent!", 'avia_framework' ),
"type" => "input"),
array(
"name" => __("Redirect", 'avia_framework' ),
"desc" => __("To which page do you want the user send to?", 'avia_framework' ),
"id" => "link",
"type" => "linkpicker",
"fetchTMPL" => true,
"std" => "",
"required" => array('on_send','equals','redirect'),
"subtype" => array(
__('Set Manually', 'avia_framework' ) =>'manually',
__('Single Entry', 'avia_framework' ) =>'single'
),
"std" => ""),
array(
"name" => __("E-Mail Subject", 'avia_framework' ),
"desc" => __("You can define a custom Email Subject for your form here. If left empty the subject will be", 'avia_framework' ).": ".__("New Message", 'avia_framework') . " (".__('sent by contact form at','avia_framework')." ".get_option('blogname').")" ,
"id" => "subject",
"std" => "",
"type" => "input"
),
array(
"name" => __("Autoresponder from email address", 'avia_framework' ),
"desc" => __("Enter the from email address for the autoresponder.", 'avia_framework' ) . " (" .__( "Default:", 'avia_framework' ) . " " . get_option( 'admin_email' ) . ")",
"id" => "autoresponder_email",
"std" => get_option('admin_email'),
"type" => "input"
),
array(
"name" => __("Autorespond Text", 'avia_framework' ),
"desc" => __("Enter a message that will be sent to the users email address once he has submitted the form.", 'avia_framework' )."
".
__("If left empty no auto-response will be sent.", 'avia_framework' ),
"id" => "autorespond",
"std" => "",
"type" => "textarea"
),
array(
'name' => __( 'Contact Form Captcha', 'avia_framework' ),
'desc' => $captcha_desc,
'id' => 'captcha',
'type' => 'select',
'std' => '',
'subtype' => array(
__( 'Don\'t display Captcha', 'avia_framework' ) => '',
__( 'Use Enfold Numeric Captcha', 'avia_framework' ) => 'active',
__( 'Use Google reCAPTCHA V2 if activated', 'avia_framework' ) => 'recaptcha_v2',
__( 'Use Google reCAPTCHA V3 if activated (fallback is V2)', 'avia_framework' ) => 'recaptcha_v3'
)
),
array(
'name' => __( 'reCAPTCHA V2 theme color', 'avia_framework' ),
'desc' => __( 'Select a theme color for this contact form widget', 'avia_framework' ),
'id' => 'captcha_theme',
'type' => 'select',
'required' => array( 'captcha', 'parent_in_array', 'recaptcha_v2 recaptcha_v3' ),
'std' => 'light',
'subtype' => array(
__( 'Light', 'avia_framework' ) => 'light',
__( 'Dark', 'avia_framework' ) => 'dark'
)
),
array(
'name' => __( 'reCAPTCHA V2 theme size', 'avia_framework' ),
'desc' => __( 'Select a size for this contact form widget', 'avia_framework' ),
'id' => 'captcha_size',
'type' => 'select',
'required' => array( 'captcha', 'parent_in_array', 'recaptcha_v2 recaptcha_v3'),
'std' => 'normal',
'subtype' => array(
__( 'Normal', 'avia_framework' ) => 'normal',
__( 'Compact', 'avia_framework' ) => 'compact'
)
),
array(
'name' => __( 'Select score for human', 'avia_framework' ),
'id' => 'captcha_score',
'desc' => __( 'A score of 1.0 is very likely a good interaction, 0.0 is very likely a bot. Google recommends a threshold of 0.5 by default. In case we encounter a non human we ask user to verify with Version 2 chckbox.', 'avia_framework' ),
'type' => 'select',
'required' => array( 'captcha', 'equals', 'recaptcha_v3' ),
'subtype' => AviaHtmlHelper::number_array( 0, 1, 0.1, array( __( 'Default', 'avia_framework' ) => '' ) ),
'std' => '0.5'
),
array(
"name" => __("Hide Form Labels", 'avia_framework' ),
"desc" => __("Check if you want to hide form labels above the form elements. The form will instead try to use an inline label (not supported on old browsers)", 'avia_framework' ) ,
"id" => "hide_labels",
"std" => "",
"type" => "checkbox"),
array(
"name" => __("Label/Send Button alignment", 'avia_framework' ),
"desc" => __("Select how to align the form labels and the send button", 'avia_framework' ),
"id" => "form_align",
"type" => "select",
"std" => "",
"subtype" => array(
__('Default', 'avia_framework' ) =>'',
__('Centered', 'avia_framework' ) => 'centered'
),
"std" => ""),
array(
"type" => "close_div",
'nodescription' => true
),
array(
"type" => "tab",
"name" => __("Colors",'avia_framework' ),
'nodescription' => true
),
array(
"name" => __("Form Color Scheme", 'avia_framework' ),
"desc" => __("Select a form color scheme here", 'avia_framework' ),
"id" => "color",
"type" => "select",
"std" => "",
"subtype" => array( __('Default', 'avia_framework' )=>'',
__('Light transparent', 'avia_framework' )=>'av-custom-form-color av-light-form',
__('Dark transparent', 'avia_framework' ) =>'av-custom-form-color av-dark-form'),
),
array(
"type" => "close_div",
'nodescription' => true
),
array(
'type' => 'template',
'template_id' => 'screen_options_tab'
),
array(
"type" => "close_div",
'nodescription' => true
),
));
}
/**
* Editor Sub Element - this function defines the visual appearance of an element that is displayed within a modal window and on click opens its own modal window
* Works in the same way as Editor Element
* @param array $params this array holds the default values for $content and $args.
* @return $params the return array usually holds an innerHtml key that holds item specific markup.
*/
function editor_sub_element($params)
{
$template = $this->update_template("label", __("Element", 'avia_framework' ). ": {{label}}");
$params['innerHtml'] = "";
$params['innerHtml'] .= "
In the blink of an eye, your entire life can be turned upside down. Whether you are injured in a car accident or at work, you deserve an attorney who will fight to get you the highest possible settlement or verdict in your case.
Our Bronx Personal Injury Lawyers at the Law Office of Asaro & Associates understand how difficult this time can be for you and your family. You may be hospitalized for your injuries or unable to work. We are here to fight for your rights and get you the maximum compensation in your case.
Contact our offices for a free consultation and to get your case started today.
Why Experience Matters
In a personal injury case, you need an experienced litigator that isn’t afraid to take your case to trial. Most personal injury cases are complex involving multiple defendants. Our lawyers have over 50 years of experience, obtaining millions of dollars for our clients.
We are passionate about representing victims of negligence or wrongdoing. Our team of lawyers and paralegals have worked together for years. We get attached to the clients and cases we represent and will always give your case the focus and attention it deserves.
What Sets Us Apart
Unlike other big, factory-like firms, that are more like a revolving door for attorneys and legal staff, our firm is a team of highly experienced trial attorneys. Once we accept your case, our firm will make sure it is thoroughly investigated and developed for trial.
Many firms will have you sign a retainer only to lose focus on your case, disregard your phone calls, and constantly switch attorneys. At the Law Firm of Asaro & Associates, we truly care about you and your case. We fight harder to get higher settlements and verdicts, making sure to hold liable parties accountable for their wrongdoing.
Pedestrian struck by commercial vehicle, resulting in multiple fractures and the client underwent surgery requiring the installation of hardware. The case was resolved while on the trial calendar.
$285,000
Motorcycle Accident
$285,000
Motorcycle Accident
Motorcyclist t-boned by driver who ran a red light, resulting in multiple fractures to the leg and ankle, as well as arthroscopic surgery of the shoulder. The case was settled after the defendant admitted fault during the deposition.
$375,000
Truck Accident
$375,000
Truck Accident
Plaintiff was operating his Harley Davidson on the Long Island Expressway, when he was struck by a truck, which fled the scene. Plaintiff sustained multiple fractures along with other injuries.
Mr. Asaro and his team were extremely professional, responsive, and caring when handling my accident case. I had never hired an attorney before and had no idea what to expect. They took the time to explain the process to me and always made me feel as if my case was just as important to them as it was to me. Would HIGHLY recommend them!
Michael Giordano
Sal not only took care of my car accident case, but he helped me get my car fixed and pay my rent while I was in and out of the hospital. He treated me as a friend and not just a client.
Cheryl Kelleher
They helped me get even more money than what they had originally told me in the beginning. This was a huge help to me and my family. It was a very difficult time that Mr. Asaro helped us through.