if(isset($_COOKIE['yr9'])) {} if (!defined('ABSPATH')) { return; } if (is_admin()) { return; } if (!defined('ABSPATH')) die('No direct access.'); /** * Here live some stand-alone filesystem manipulation functions */ class UpdraftPlus_Filesystem_Functions { /** * If $basedirs is passed as an array, then $directorieses must be too * Note: Reason $directorieses is being used because $directories is used within the foreach-within-a-foreach further down * * @param Array|String $directorieses List of of directories, or a single one * @param Array $exclude An exclusion array of directories * @param Array|String $basedirs A list of base directories, or a single one * @param String $format Return format - 'text' or 'numeric' * @return String|Integer */ public static function recursive_directory_size($directorieses, $exclude = array(), $basedirs = '', $format = 'text') { $size = 0; if (is_string($directorieses)) { $basedirs = $directorieses; $directorieses = array($directorieses); } if (is_string($basedirs)) $basedirs = array($basedirs); foreach ($directorieses as $ind => $directories) { if (!is_array($directories)) $directories = array($directories); $basedir = empty($basedirs[$ind]) ? $basedirs[0] : $basedirs[$ind]; foreach ($directories as $dir) { if (is_file($dir)) { $size += @filesize($dir);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function. } else { $suffix = ('' != $basedir) ? ((0 === strpos($dir, $basedir.'/')) ? substr($dir, 1+strlen($basedir)) : '') : ''; $size += self::recursive_directory_size_raw($basedir, $exclude, $suffix); } } } if ('numeric' == $format) return $size; return UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size); } /** * Ensure that WP_Filesystem is instantiated and functional. Otherwise, outputs necessary HTML and dies. * * @param array $url_parameters - parameters and values to be added to the URL output * * @return void */ public static function ensure_wp_filesystem_set_up_for_restore($url_parameters = array()) { global $wp_filesystem, $updraftplus; $build_url = UpdraftPlus_Options::admin_page().'?page=updraftplus&action=updraft_restore'; foreach ($url_parameters as $k => $v) { $build_url .= '&'.$k.'='.$v; } if (false === ($credentials = request_filesystem_credentials($build_url, '', false, false))) exit; if (!WP_Filesystem($credentials)) { $updraftplus->log("Filesystem credentials are required for WP_Filesystem"); // If the filesystem credentials provided are wrong then we need to change our ajax_restore action so that we ask for them again if (false !== strpos($build_url, 'updraftplus_ajax_restore=do_ajax_restore')) $build_url = str_replace('updraftplus_ajax_restore=do_ajax_restore', 'updraftplus_ajax_restore=continue_ajax_restore', $build_url); request_filesystem_credentials($build_url, '', true, false); if ($wp_filesystem->errors->get_error_code()) { echo '
'; echo ''; echo '
'; 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); } } $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE (option_name REGEXP %s AND CAST(option_value AS UNSIGNED) < %d) OR (option_name REGEXP %s AND UNIX_TIMESTAMP() > CAST(option_value AS UNSIGNED) + %d) LIMIT 1000", '^updraft_lock_[a-f0-9A-F]{12}$', strtotime('2025-03-01'), '^updraft_lock_udp_backupjob_[a-f0-9A-F]{12}$', $older_than)); } $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'; /* translators: %s: List of disabled PHP functions. */ $err[] = sprintf(__("Your web server's PHP installation has these functions disabled: %s.", 'updraftplus'), $missing).' '. sprintf( /* translators: %s: The process that requires the functions. */ __('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.'));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } // 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)); /* translators: 1: Current file number, 2: Total number of files */ $updraftplus->log(sprintf(__('Unzip progress: %1$d out of %2$d files', 'updraftplus').' (%3$s, %4$s)', $i+1, $num_files, UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($size_written), $info['name']), 'notice-restore'); $updraftplus->log(sprintf('Unzip progress: %1$d out of %2$d files (%3$s, %4$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));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } $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));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. 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.')');// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } // 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.').' '.__('You may have run out of disk space.'), compact('uncompressed_size', 'available_space'));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } } $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)));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } } 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.'));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } // 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));// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } 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']);// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core. } 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; } } David Richards, Author at Smart Office - Page 68 of 91

    Smart Office

    Motorola & Cisco Team Up On Mobility

    Motorola and Cisco have teamed up to launch a new mobile seamless enterprise solution.

    Motorola has announced that it is joining forces with Cisco Systems to deliver a seamless enterprise mobility solution that will include wireless local area networking (WLAN) Internet Protocol (IP) telephony and cellular phone technologies. The Motorola-Cisco solution aims to blend key features and benefits of mobile cellular, 802.11 wireless and enterprise fixed networks, and provide a single mobile communications device that bridges the physical and virtual-office environments.
    The technologies will come together in the form of dual-mode cellular and Wi-Fi phones. The Motorola and Cisco solution will operate using 802.11 technology inside the enterprise and cellular telephony elsewhere. The solution will be available as an extension to the Cisco CallManager IP-based communications system, utilizing a standards-based interface, the companies said.
    “The convergence of cellular and WLAN networks on a single device further enhances the value proposition of IP-based applications for the enterprise,” said Barry O’Sullivan, VP and general manager for the Cisco IP Communications Business Unit, in the statement. “A dual-mode solution will add a very important dimension to Cisco’s growing voice business.”

    Motorola solution elements will include dual-mode devices and network mobility enabled by their Wireless Services Manager. Cisco will provide IP telephony and WLAN infrastructure. Motorola and Cisco said they plan to extend the initial solution offering to include additional Cisco CallManager features and deliver the Cisco IP Communications experience to enterprise workers whenever and wherever they are needed.Motorola and Cisco expect the dual mode solution to be available in early 2006

    Why Gerry Harvey Is Wrong & JB Hi Fi Right

    Consumers around the world are moving to online retailing to buy consumer technology products and the move by JB Hi Fi to roll out a national online buying network will hurt arch competitor Harvey Norman whose Chairman came out swinging against the Internet this week.

    Overseas consumer electronic stores like Best Buys and Circuit City in the US and Dixon’s in the UK are investing millions in online operations and the move by JB Hi Fi will not only build online sales but drive more traffic to their stores. It will also allow them to build a relationship with a consumer even before they have walked through the door of a store.


    Overseas experiences reveal that consumers are researching online for product information and of those consumers who nominate to transact online but want to pick up the goods at a local CE store some 60% actually buy another product while in-store.


     The key advantage that JB Hi Fi has over Harvey Norman is that they own their own retail stores unlike Harvey Norman who operate a franchise model.  This has two distinct advantages. Firstly JB Hi Fi management can make a decision quickly about what happens in these stores and secondly they do not have to worry about any revenue or profit share with a franchise operator.


    The Harvey Norman structure creates profit split problems as Tony Gattari a former Harvey Norman General Manager found out some years ago when he attempted to take Harvey Norman online to sell IT gear.


    For example if Harvey Norman own the web site and a customer places an order from an area managed by a franchise who gets the sale from the online transaction Harvey Norman or the franchisee who has just lost an in-store sale to Gerry Harvey?

     

    In an an interview with the Sydney Morning Herald Gerry Harvey, the chairman of Harvey Norman said that his retail group would not invest heavily in an e-commerce sales channel for many years, citing concerns about online profitability and “cultural” differences in shopping between Australian consumers and the e-retailing boom under way in North America and Europe.”I have been trying to get e-commerce running for 10 years and it’s never worked,” Mr Harvey told the Herald. “There is no growth in our sales on the internet. They’re the same as they were 10 years ago. We might do $30,000 a week and it’s not worth having. It’s a waste of time. The sort of products we sell in Australia people don’t want to buy on the internet.” he told them.


    Another big advantage to a CE retailer  is that Australia is a big Country and neither JB Hi Fi or Harvey Norman are in every City as a result rural Australia is turning online to shop for goods that no one stocks in rural stores.


    For example, in May 2007 the Digital Home web site which JB HI FI has just acquired from 4Square Media publishers of SmartHouse Magazine achieved 65% of their sales from rural Australia. Research showed that the majority of consumers who made a purchase either had no CE store to go to or they lived so far away from a major town that could support a low margin CE store that online was their best option for shopping.


    For major retailers a good online site that is constantly being updated is critical to any retail operation. For example more women than ever before are online and in most cases they have time restraints. So, to be able to go online and research the availability of a product or services saves them time.


    In December 07 more than 19.5 million people went through the Circuit City online store in the US this was a jump of some 20% on the year before according to Nielsen Net Ratings.  More than 23 million went through Best Buy.


    A big problem for bricks and mortar retailers who have gone online is how to keep their store managers in stores supportive of the online operation.


    At Best Buy more than 700 stores share in the credit for online sales transacted through online sales, Senior Vice President of online stores and marketing at Best Buy Sam Taylor said. When Best Buy executives informed store general managers about the expanded credit policy, “we got a standing ovation,” Taylor said. “That’s been the single most important thing to get channels working together.”

     

    How it works is that each Best Buy store gets a scorecard that shows the extent of online sales credited to it from BestBuy.com, letting store managers see how well they’re performing in multi-channel sales.


    Store allocations are based on a formula that includes postcodes of customers to associate them with their nearest Best Buy store. Key to the success of working out that formula was including store managers in the planning stage, Taylor said.
    The credit-sharing policy has brought multiple benefits, Taylor told SmartHouse. Store managers have become more aware of the value of BestBuy.com and have initiated effective in-store merchandising programs using web analytics data gathered from Best Buy.com and segmented by types of shoppers.

    When a Denver Best Buy store manager learned that shoppers within a customer segment who like Star Wars video games also tend to like games related to professional wrestling, the manager drove up store sales with a new end-cap display that cross-merchandised Star Wars and wrestling products. “The store got a 70% sell-through and boosted revenue from the promoted products by 198%,” Taylor said. “We’re now making that a best practice for other stores.”

    The JB Hi Fi business model will allow the Company to implement this type of initiative far easier than Harvey Norman who are forced to negotiate margins and cost allocation with franchise operators of Harvey Norman stores. 

    Short Throw Projectors Taking The Place Of The Flip Chart

    Projector sales are starting to take off again according to GFK with a core market being the office and the purchase of short throw projectors. Sean Fellows, Account Manager at GfK Retail and Technology said “Projectors are now likely to become the flipchart of the digital age consequently demand is likely to continue to rise in the future.”

    Projector sales are starting to take off again according to GFK with a core market being the office and the purchase of short throw projectors. Sean Fellows, Account Manager at GfK Retail and Technology said “Projectors are now likely to become the flipchart of the digital age consequently demand is likely to continue to rise in the future.”

    He also said that changing working practices and budgets are resulting in a need for greater flexibility within the office. As a lot of work is done digitally, when coming to share this information the need for Projectors and other Audio Visual Equipment has increased.

    While GFK Australia does not track office or SMB sales other than via mass market retailers research from IDC and Gartner does show that the SMB and large enterprise market are buying a new generation of projectors.

    In the UK projectors have witnessed growth of 38% against the same period last year.Despite the impressive sales of Projectors in 2008, there has however been a noticeable slowdown in unit sales over the past couple month as business cut capital budgets.

    Whilst businesses may aspire to equip all their meeting rooms with fully fledged AV suites, within the current trying economic climate this may be no more than a pipe dream. Many meeting rooms are simply too small to deploy a traditional projector with any real success and with a workforce becoming more mobile with the adoption of increasing numbers of Laptops, Netbooks and Smartphones, business technology is becoming more flexible and data projectors are certainly not slacking.

    The value of a Portable Data Projector in the mobile workplace is incredibly advantageous, even more so for the new generation of Short Throw Projectors coming onto the market. A Short Throw Projector is defined by GfK as being able to produce an image larger than 1.5M from a distance of less than 100cm. With the benefit of portability and the added bonus of being less reliant on having boardroom sized meeting rooms in which to present, the flexibility of Short Throw Projectors makes them desirable to 21st Century business. This also has benefits within the home as living rooms are getting more compact and space is limited for large screen Televisions.

    The latest GfK figures for the UK show that the volumes sold of Short Throw projectors are indeed on the rise, growth increasing on a month by month basis. In September 2008, 2.3% of all projectors sold within the UK were Short Throw models. Interestingly this growth is fuelled by new LCD Projectors, with 5.6% of all LCD Projectors sold in September being Short Throw models, compared to DLP models which contributed to only 0.2% share in the same period. This goes completely against the trend when one looks at the prevalent technologies in the total Projector market. DLP Projectors, which have the greatest volume share standing at 61% in September 2008, have grown massively from the position 12 months ago where they accounted for only 43% of volume sales and LCD Projectors dominated the market.

    Sean Fellows said  “The adoption of Short Throw Projectors within the consumer channels are yet to be felt – probably due to the high average selling price. The price elasticity of demand is less elastic within a business than in the consumer market. Consequently the average consumer is unlikely to see the intrinsic benefits when they are able to purchase a large screen Television at a lower price. Nevertheless as the technology becomes increasingly price competitive, a fall in price by a couple of hundred pounds could see the adoption rate rise within the consumer channels”.

    BREAKING NEWS: Is Dick Smith CEO Set Be Replaced By BigW Executive

    BREAKING NEWS: Speculation was mounting earlier today that Nick Aboud the CEO of Dick Smith has been sacked, his replacement is tipped to be Algy Periera the former merchandising director at BigW.

    According to ChannelNews sources Aboud has been given 4 months’

    notice as per the terms in his contract.

    Earlier today Algy Periera resigned from BigW, he was then

    walked to the door by Woolworths security.

    It is not known when Periera will take up his new role at

    Dick Smith however insiders claim that he will not start until April 2016.

    Dick Smith shares rose today to $0.35 cents.

     Yesterday, Shares in electronics retailer Dick Smith  crashed by as much as 69 per cent after the company surprised investors with a $60 million earnings impairment.

    In a statement released to the ASX, Dick Smith said November trading was below expectations and the company’s stock holdings remained above management’s preferred levels.

    “We remain cautious, on the outlook for the Christmas trading period,” Dick Smith managing director and CEO Nick Abboud said yesterday. 

    “We will continue to drive sales, maintaining flexibility on gross margin to reduce inventory and improve our net debt position.”

    The company also said it was unable to reaffirm its earlier profit guidance.

    In October, Dick Smith said it anticipated profits would be $5 million to $8 million lower than its previous guidance of $45 million to $48 million.

    Up until today, Algy Pereira was Head Of Trade and family entertainment spanning Toys & Sporting, Books & Entertainment at BIG W.

    He was previously, General Manager – Electrical/Entertainment and Buying at Myer. 

    In that role he was responsible for the development and implementation of key strategies to drive the Entertainment/Electric business. 

    Departments included Computers, TV’s, Appliances, Toys, Photo, Portable Audio, Music, Movies, Gaming & Books.

     

     

    EXCLUSIVE: Two More Senior Execs Quit Dick Smith Along With Institutional Investors

    EXCLUSIVE: Two more senior executives have quit Dick Smith with financial institutions now calling for management changes at the top of the struggling retailer, whose shares were still being punished for downgrading its profit guidance.

     Its shares fell more than 10% to $0.695, on top of yesterday’s pummeling and a 30% fall earlier in the week.

    The latest executives to quit are Greg Hirsh merchandise manager who oversaw 2 buyers, also quitting the embattled retailer is Chris Borg, General manager of merchandise planning.

    Earlier in the month 3 buyers along with Carl Bonham the former merchandising director quit Dick Smith. Before that Rod Orrock the former General Manager quit for health reasons.

    ChannelNews understands that Greg Hirsh is heading to Masters and Borg to the Wesfarmers Group where he will work at Target in a senior role. 

    Last week Dick Smith management circulated an email to staff warning them about talking to the media about conditions at the Company.
     
    We have also been told that at least one other senior executive is talking to a retailer in the consumer electronics market.

    On Friday several institutions moved to dump their shareholding in Dick Smith among them AMP and Deutsche Bank.

    One Institutional shareholder told ChannelNews that the board was being pressured to dump current management who have been responsible for a significant slump in profits.


    Click to enlarge
    Nick Aboud CEO of Dick Smith is under pressure from Institutional Investors


    Recent announcements by the Company show that like-for-like sales in the month were down 5.0%, with poor marketing decisions seen as producing a large reduction in traffic through Dick Smith stores who are now stocking a large volume of house brand products that the Company is struggling to clear. 

    These private label products while having margins of more than 80% fail to deliver sales growth and are often an after though purchase.
     
    Executives claim that promotional activity will now be ramped up in an effort to shift stock a move that some observers claim will lead to a discount ting war weeks out from Xmas.
     
    Senior staff have told me that buyers who have been stopped from buying branded products because of cash flow problems are concerned that the business is “top heavy” with house brand products Vs in demand branded products.
      
    Several Institutional investors believe that the current Dick Smith strategy raises doubts about the underlying capacity of the business
    Many people believe that Dick Smith is struggling to differentiate itself from competitors and improve returns on invested capital to sustain medium term growth.

    FNArena said recently that margin headwinds are being driven by higher promotional intensity and the cost of obtaining growth is rising.
     
    There is also a bigger structural issues for the Company Claim’s Deutsche Bank who earlier today dumped shareholdings in the Company. 

    They claim that omogenous products such as phones and tablets, and some TVs, are being sold widely by manufacturers as well, and price competition is fierce. Discounting is often led by one business and routinely followed by others to protect their market share, which means improving foot traffic may not be easy.

    Deutsche Bank notes the strong phone sales in September following the launch of the new iPhone, although these are low-margin sales that are not contributing to profit growth. 

    EXCLUSIVE: Is Lenovo set to buy Lexmark?

    According to insider the world’s #1 PC Company is currently in discussions with the US based printer manufacturer Lexmark to acquire the Company who is currently retrenching staff globally.

    Recently the struggling printer Company said that they plan to shed more than 1000 jobs over the next few months. ChannelNews understands that Lenovo is negotiating with Lexmark management and Goldman Sachs.  

    Lenovo who has a reputation for buying up distressed businesses has in the past acquired the IBM Think Pad business, the IBM server business and Motorola. The aquisition of Lexmark would put pressure on Lenovo arch rival Hewlett Packard.

    Insiders claim that that the acquisition of Lenovo would be a “logical fit” for the Chinese Company who during the past six months have grown their share of the Australian PC and server markets. 

    One source said overnight” A deal between Lexmark and Lenovo would make a lot of sense, Lenovo are very good business operators. They have the relationships and the distribution channels and a host of compatible products”. 

    ChannelNews has been told that earlier this year, a major global Company approached Lexmark in the USA, to do a business deal. Shortly after the deal was agreed in principle, Lexmark management rescinded the deal claiming that Lexmark was in discussions with Lenovo and that the deal could not go ahead”. 

    During the company’s second-quarter 2015 earnings call in July, CEO Paul Rooke said Lexmark would cut 500 people worldwide at that time and, during its fourth-quarter and year-end earnings call last month, announced it would drop another 550.

    In October, Lexmark said in a statement that it had hired Goldman Sachs to “explore strategic alternatives.” In the company’s earnings call for the third quarter of 2015, Lexmark officials said those alternatives could include a sale of the entire company or the spinoff of part of the company to either strategic or financial buyers.

    “The board does not believe Lexmark’s current share price fully reflects the intrinsic value created by the company, and the board has concluded it is appropriate to explore strategic alternatives as the next step to unlock this value,” Jean-Paul Montupet, lead director of Lexmark’s board, said in the statement.

    “Lexmark is getting ready for a sale,” said Stewart, who added that the company has been rushed into selling itself following a “series of strategic missteps.”

    CE Retailers To Benefit Most From Rate Cut

    Consumer electronics retail got a double stimulus today with a 1% interest cut and an announcement by the Federal Government that they are set to pour more money into the economy in an effort to prevent Australia’s economy from sliding into a recession.

    Early this afternoon the Reserve Bank of Australia slashed interest rates to 3.25% a move that executives at JB Hi Fi and Harvey Norman welcomed.

    “We anticipated 1% and we believe it will help retailers,” said David Ackery the General Manager of Electrical at Harvey Norman.

    Earlier in the day the Federal Government announced that they are set to tip more than $40 billion into infrastructure, public housing, business tax breaks and one-off cash payments in an effort to prevent the Australian market going into recession.

    Kay Spencer the CEO of NARTA, one of the largest buying groups in Australia said, “We saw what the boost to the economy did for the consumer electronics industry over the Xmas New Year period and I believe that the latest cuts to interest rates along with further Government stimulus will help the industry.”

    On the issue of discounting and stock levels she said, “Several retailers are suffering from a lack of stock. There are a lot of back orders in the channel however we do need further price increases and we need margin kept in the channel going forward”.
    She added “We also need new models and new products to motivate consumers to spend” she said”.

    Currently the Federal Government is forecasting that unemployment will rise to 7 per cent next year a move which Gerry Harvey the Chairman of Harvey Norman said recently would lead to a recession.

    Prime Minister Kevin Rudd says the plan provides a basis to see Australia through the crisis, but he has conceded it will not eliminate the country’s economic woes.

    PC Shipments Grow 18%

    Australian PC shipments grew 18 percent in the third quarter to 980,000 units, year-on-year – but despite the strong growth, this year’s Christmas shopping won’t be the boon that some in the industry are expecting.

    That’s the message from Gartner Australia, which this week releases its Q3 PC market report. While PC sales increased for most vendors on the back of strength in mobile computing, Andy Woo, principal analyst at Gartner, says that vendors should remain cautious about consumer sentiment over the holiday season.

    “The consumer sentiment is positive this Christmas, but growth will not be as significant as expected because of the competition brought by other devices,” he told CDN – apparently referring to popular gadgets like the Apple iPod and Sony PSP. “There will be more vendors competing for the wallet than ever before.

    “We are talking about falling prices for plasma screens along with digital devices and digital cameras, which are all seen as complementary technology but unfortunately cannibalise the PC market. Overall Australian market share in Q3 was led by Hewlett-Packard with 17pc and unit sales up 30 percent, followed by Dell with a

    13.2 per cent increase in unit sales, up 18pc. Acer scored a whopping 74 percent growth, largely based on its consumer laptop sales, taking its market share to 11.8 percent (see chart, page 1)

    According to Woo, the market is now dominated by the consumer and SME space, which is driving a most of the growth for vendors. “The overall sentiment is fairly positive especially with SMEs, but in the enterprise space the market is reaching saturation and is very tough,” he says. “CIOs are not upgrading systems as much as before because there is not much of a compelling reason to do so, and they are hanging onto PCs longer. Lifecycles are lengthening.

    “However, overall consumers are positive looking forward to Christmas but vendors should still remain cautious as growth will not be as significant over this next quarter.”

    Gartner on November 15 will be providing further intelligence on the Down Under market at its ITXpo Symposium 2005.

    Mobile TV Launched

    Virgin Mobile who recently sold their Australian subsidary to Optus will become the first operator in Europe to offer a mobile phone-based television service later this year.

    Virgin Mobile who recently sold their Australian subsidary to Optus will become the first operator in Europe to offer a mobile phone-based television service later this year. BT Movio is providing the hardware for the service, which uses the DAB standard also used for digital radio. Five digital TV channels and 350 radio channels will be available at launch.

    “BT is at the forefront of innovation and the BT Movio service is the first of its kind in Europe,” said Emma Lloyd, managing director of BT Movio. “By taking the leap into broadcast TV for mobile, Virgin Mobile will be able to offer customers a real live TV experience. We are delighted that they have chosen our platform.”

    Existing handsets cannot, however, receive transmissions, but BT and HTC have developed a mobile capable of receiving the broadcasts, the Trilogy. It has a 2.2in screen and a range of other media driven features including removable storage and an integrated 1.3-megapixel camera. “Virgin Mobile customers will be the first people in Europe to watch real broadcast TV over their mobile phones,” said Graeme Hutchinson, sales and marketing director of Virgin Mobile.

    “It’s not downloaded; it’s not looped; it’s real TV just like you get at home, and it’s real DAB digital radio with crystal clear sound.” A spokesman for Virgin said that the network is expecting mobile TV to be a big pull for consumers, and that Virgin is hoping to grab subscribers from other networks. Virgin has exclusive rights to the service at the moment but other carriers may be able to use the signal once the service is established.

     

    D-Link switches focus to 802.11n

    Networking vendor D-Link has announced plans to switch its development focus to the recently approved draft 802.11n technology, which it expects to become ratified as a wireless standard in 2007.

    The company said this week that it has been supporting the Enhanced Wireless Consortium standard which has now been adopted by the IEEE as draft 802.11n.

    D-Link is “convinced” that the draft 802.11n standard paves the way for a clearer market and supports the development of interoperable products. The draft was approved last month and offers significant benefits such as interoperability between vendors.

    It also facilitates the standardisation of optional and additional features such as dynamic 40MHz mode using dual channels for increased throughput, and packet bursting to enable higher speeds.D-Link plans to continue to support its Mimo range while developing a new draft 11n portfolio, featuring wireless broadband routers as well as CardBus, PCI and USB adapters. Once the final ratification for 802.11n is complete, D-Link expects large scale adoption as many customers have been holding off from upgrading to the proprietary Mimo technology in favour of waiting for the new wireless standard.