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 76 of 91

    Smart Office

    Google Rakes In Billions In Profits In Q2 OZ Profits Not Disclosed

    Google who is raking in tens of millions from Australian retailers every month has reported $3.93 billion in profits for the quarter.

    Currently under investigation by the Australian Tax Office and facing a Senate Inquiry into their Australian tax minimisation scheme Google has reported second-quarter net income of $3.93 billion, this is up from $3.35 billion from the same period 12 months ago.


    Revenue rose 11% to $17.73 billion. Excluding traffic-acquisition costs-the revenue Google shares with other companies that syndicate its search results-revenue totaled $14.35 billion, above the average analyst forecast of $14.27 billion.

    Excluding currency fluctuations, Google said its revenue in the June quarter would have improved 18%.

    The company’s new financial chief, Ruth Porat, who joined from Morgan Stanley in May, said in prepared remarks Thursday that the latest results reflected “continued growth across the breadth of our products, most notably core search, where mobile stood out, as well as YouTube and programmatic advertising.”

    CE + Appliance Suppliers Set To Benefit From WA +SA Push By Aldi

    Australian distributors who are supplying Aldi with consumer electronics and appliances are set to get a massive lift in sales as Aldi looks to spend $750 Million pushing into Western and South Australian.

    Organisations such as Tempo and Laser Corporation are set to

    benefit from the brutal battle that is currently being played between

    supermarkets.

    Also set to benefit distributors and CE and appliance vendors

    will be the emergence of Aldi arch rival Lidl, this is the discount chain which

    just beat Aldi to be named Britain’s best supermarket.

    Lidl, ranked by Deloitte as the fourth-largest retailer in

    the world with $128 billion in annual sales, has already met with several

    distributors in Australia as well as branded CE and appliance vendors.

    ChannelNews has also been told that Woolworth’s management

    are looking at the implementation of dump bins in their supermarket stores similar

    what Aldi does to shift products such as TV’s, small appliances and tools.

    A report prepared for Woolworths acknowledges that the mix

    of food and other goods such as discount cosmetics, tools and clothing has

    helped Aldi build traffic especially when stores are located in locations that

    generate high traffic during lunch times.

    Currently Woolworths is selling mixed stock via their

    struggling Big W and Masters stores. 

    LG And Dyson Bitter Enemies, After LG Kompressor Vacuum Adv Banned

    The battle between LG and Dyson as to whose vacuum product sucks the best has been ongoing across several continents for years.

    Back in November 2007, an LG vacuum cleaner adv was pulled by the British advertising watchdog, after Dyson took action against LG Electronics.

    Now the two are at it again after LG claimed in a Federal Court filing that advertising for the Dyson V6 currently being sold in Australia was misleading. 

    In a late statement LG Australia said:

    “LG Australia has initiated proceedings in the Federal Court of Australia against Dyson to restrain Dyson from making certain claims in relation to its V6 Cordless Vacuum. 

    “LG Australia alleges that Dyson’s advertisements claiming that the Dyson V6 cordless vacuums are the “the most powerful cordless vacuums” and that those cleaners have “twice the suction power of any cordless vacuum” are misleading on the basis that the LG CordZero cordless canister vacuum is in fact more powerful and has more suction power than the Dyson V6 cordless vacuum.  

    “The matter is currently being considered by the Court, and as such, LG is not in a position to make further comment.”

    In the UK in 2007 the Advertising Standards Authority (ASA) declared that LG marketing for their Kompressor vacuum cleaners misled consumers about the effectiveness of its dust compression technology after Dyson called for an investigation. 

    The ASA ruled the TV commercial was in breach of UK standards in three ways: (1) the tagline ‘compress your dust’ was misleading because the appliance’s compression system only compacted large material such as fluff, while fine dust was stored in another chamber altogether; (2) the ad misleadingly implied that collected dust was compressed into a solid mass, but the fine dust chamber was not shown in the ad; and, (3) the ad was potentially denigrating to competitors’ products because it implied they produced a larger dust cloud than the LG Kompressor.

    All three of Dyson’s complaints were upheld by the Advertising Standards Authority (ASA) after an independent expert called in by the regulator agreed that the Korean giant’s Kompressor vacuum cleaner TV commercial was misleading.

    “We told LG that the ad should not appear again in its present form,” said the ASA in concluding its adjudication.

    At the time Dyson claimed that because fine dust is not compacted in the Kompressor’s main chamber but stored in a separate compartment, dust was actually more susceptible to dispersion when emptied and therefore more likely to create a dust cloud than other bagless machines.

    “Our engineers focus on developing and testing Dyson technology, but we do keep half an eye on competitors,” said Dyson UK group marketing director, Clare Mullin.

    “We’ve come to expect lazy innovation masked behind misleading marketing, and this seems to be what we have here from LG. 

    At the time LG Electronics Australia who are now taking on Dyson claimed that the UK ASA rulings had no relevance to the Australian market.

    LG Australia chose not to run the banned TV commercial in Australia said Paul Jenkins the then General Manager of LG Marketing. 

    Back in 2012 LG Electronics produced a TV commercial for

    their Kompressor vacuum cleaner that appeared to suck fat out of a female

    model.

     

    Top End Toshiba L40W Radius Notebook Panned By The Australian

    Toshiba who are struggling to get their act together in the consumer notebook market has seen their premium convertible Radius L40W notebook described as expensive having an “ordinary” screen” and an awkward tablet component by Australia’s national newspaper.

    Under siege from the likes of Lenovo, whose top end  products are running hot off the shelves at JB Hi Fi, the new Toshiba Radius L40W is the product that Toshiba management was hoping would take it up to the highly popular Lenovo Yoga Pro 3, which is proving so popular at $2,300 that it has resulted in a 40% lift in premium notebook sales at JB Hi Fi and Harvey Norman. 

    Writing in the Australian newspaper, seasoned technology journalist Chris Griffith described the Radius L40 W as “Nothing noteworthy except price”. 

    The review that was set up by Ogilvy PR who won the Toshiba account back last year, the PR Company has been selective in who has been able to do a review of the top end Toshiba notebook.

    Griffith said that the notebook failed to impress in several areas.

    He described the 360 degree tablet as “awkward” because the screen and keyboard can’t be separated.

    This resulted in the keyboard sitting underneath the screen with the keys facing downwards. 

    He said this made it uncomfortable on your lap and you can accidentally trigger the keys.

    He also described the overpriced notebook and tablet as heavy at 2kg, he pointed out that the weight was four times the weight of an Apple iPad Air.

    Toshiba are still using a USB2.0 port in a $1799 machine a move that “perplexed” the reviewer.

    Griffith wrote “There’s also a curious Windows button on the left-hand side that switches from the standard desktop to Windows tiles mode. I don’t see the need for this when you have a Windows key on the keyboard”.

    He added “Given its $1799 price tag, you’d expect 2015-style innovation: maybe impressive battery life with new, energy-efficient chips, maybe a cutting-edge USB-C port for ultrafast data transfer, maybe a biometric fingerprint reader that eliminates passwords, or a cutting edge design such Dell’s XPS (2015) with its ultra-thin bezel. But nothing stands out”. 

    He also described the 1366 x 768 pixel, 720p display screen as “ordinary” especially as several brands such as Acer and Asus are delivering significantly better display screens. 


    How the Australian saw the Toshiba top end offering. 


    After extensive use Griffith only got six hours and 15 minutes battery life which means that on a flight to Asia the battery will run out without a top up. 

    Apple’s MacBook Air offers 9-12 hours battery life and Lenovo ThinkPad X250 is reported to last more than 15 hours.

    As for the 14-inch, 720p display itself, it is well below full high-?definition or the 2K definition quality you’d expect in a top-line notebook and incredibly reflective.

    The review unit was a top end specked machine with an Intel Core i7 5500U processor at 2.40 gigahertz, 8 gigabytes of DDR3 memory, on-board Intel HD5500 graphics, fast 802.11ac wireless and Intel WiDi.

    The CPU scored 285 cb which is at the lower end of the Core i7 range.

    Giving the device just 5/10 he described the Toshiba Radius L40W as a highly priced mid-range laptop. It’s a convertible, sure but as the flagship of Toshiba’s new Australian range, it is a disappointing device.

    Rating: 5/10

    Price: $1799 for Core i7 model

    JB Hi Fi Reports Record Results Despite Dick Smith Collapse

    JB Hi-Fi has reported half year sales grow of 7.7%, Sales for January 2016 were up 10.2%, online sales are up 28.9%.

    Gross profit increased 7.6% to 95.2M on revenues of $2.2 Billion. 

    Gross margin down 3 bps to 21.7% (HY15: 21.7%). Cost of doing business was 14.3% (HY15: 14.2%), resulting in EBIT of $138.2 million (HY15: $130.0 million) and an EBIT margin of 6.5%
    (HY15: 6.6%).

    A major contributor to the margin downturn is believed to be a move by Dick Smith to heavily discount stock running into the peak buying period of Xmas and the New Year.  

    JB HI-FI CEO, Richard Murray, said “This was a solid result with trading in the important November and December periods particularly strong as we executed on a great promotional plan.”

    The Company opened seven new stores in the period with the Melbourne based Company set to open a total of eight new stores in FY16 and maintains its stated target of 214 stores across Australia and New Zealand.

    Of these 194 stores, 56 were JB HI-FI HOME stores, with four new JB HI-FI HOME stores opened and nine existing JB HI-FI stores converted to JB HI-FI HOME during HY16. The Company is targeting a total of circa 75 JB HI-FI
    HOME stores across Australia and New Zealand.

     JB HI-FI CEO, Richard Murray, said “Each new JB HI-FI HOME store contributes to growing our customer awareness, market share and supplier support.”

    In addition to the HOME store roll-out, the Company continues to introduce small appliances to its existing store network as a natural progression of its proven home appliances strategy. Small appliances were introduced to 22
    existing JB HI-FI stores during HY16, with up to an additional 15 existing JB HI-FI stores expected to range small appliances by the end of FY16.

    The home appliances market in Australia is circa $4.6 billion, larger than many of the other categories JB HI-FI operates in, and presents a significant opportunity for the Company as it leverages the strength and trust in the JB
    HI-FI brand. 

    JB HI-FI CEO, Richard Murray, said “Appliances are a natural adjacency to our successful consumer electronics categories and accessing the $4.6 billion appliance market, via both the introduction of small appliances
    to existing JB HI-FI stores and the HOME store conversions, is a significant growth opportunity for the Company.”

    Out of Store
    Online sales continue to grow, up 28.9% in HY16, and represent approximately 3.0% of total sales (HY15: 2.5%).

    Unique visitors to JB HI-FI’s websites during the 12 months to 31 December 2015 averaged 1.3 million per week.

    JB HI-FI Solutions remains on track to deliver on its longer term aspirational sales target of approximately $500 million per annum, through both organic growth and strategic acquisitions. All departments achieved solid growth
    for the period.

    “JB HI-FI Solutions is a key driver of our future growth. We continue with our aggressive recruitment plan as we expand our product and service offer” said JB HI-FI CEO Richard Murray. 


    JB HI-FI CEO, Richard Murray, said “Sales in January 2016 were pleasing given the strength in the prior year, with back to school technology purchases in both our retail and Solutions businesses driving sales.”


    Murray said that the market is expected to remain competitive as retailers cycle a strong second half in FY15.

    $37M DJ’s Accuser Exposed For Prior Sexual Harassment Claims

    A former Optus employee who is suing retailer David Jones and its former CEO for sexual harassment has been exposed as having made prior harassment claims while working for the NSW Police.

    Publicist Kristy Fraser-Kirk is claiming $37 million in damages against David Jones and former CEO Mark McInnes who she claims made sexual advances to her while employed at the retailer.
    Now it’s been revealed that Fraser Kirk made a similar complaint against her boss SSgt Michael Magill while working as a civilian for the NSW Police.
    Questions are also being raised about the judgement of her PR adviser Anthony McClellan a former A Current Affair and Sixty minutes Producer. McClellan has been accused by the Sunday Telegraph of dishing out “dreadful advice” to Fraser-Kirk who a called a press conference on the day prior to David Jones Spring fashion showing.
    McClellan who has a history of poor judgement once told A Current Affair researcher to “go away” when a story was put to him. The researcher then took the story to senior management who backed the researcher. The story won a Logie. 
    According to the Sunday Telegraph Fraser-Kirk accused the police officer of sending her inappropriate text messages. The formal complaint was investigated by two police inspectors.
    Sgt McGill is still employed by the NSW Police Force.
    In her latest attempt to extract cash from David Jones and McInnes, Fraser-Kirk has had her advisers set up a hot line in an effort to get other David Jones staff to complain.

     
    However several former police colleges of Fraser Kirk have come forward claiming that she tried to solicit them to complain when she laid a complaint against her former police boss.
    The women are said to be furious at her actions claiming that she went out of her way to try and smear SSgt Michael Magill.
    “She tried to make out he was a sleaze. She wanted us to complain and make a stronger case against him” one of the women said.
    None of the women supported her claims.
    Investigations are also being made at Optus where it is believed she also made complaints.
    Both Anthony McClellan and Kristy Fraser-Kirk are refusing to comment on the latest allegations.

    Harvey Norman Technology Pricing “Obscene” Claims Shoppers

    Outraged shoppers have described Harvey Norman pricing for consumer electronic goods as obscene after they were caught flogging a Microsoft mouse for $68, $20 more than what Officeworks sells it for and $8 over the Microsoft recommended retail price.

    It’s also been revealed that a Kaiser Baas hoverboard is $150 more expensive at Harvey Norman than at JB Hi Fi, Harvey Norman are selling the popular, but controversial device for $698 Vs $548 at JB Hi Fi.

    This is not the first time that SmartHouse has identified Harvey Norman as one of the most expensive retailers in Australia to buy CE products. 


    Click to enlarge


    Click to enlarge



    This weekend Social Media turned on Harvey Norman over their pricing of a Microsoft Sculpt Touch Mouse which was advertised for $68, $8 over the reccomended retail price.


    Click to enlarge
    Harvey Norman price


    Click to enlarge
    The reccomended retail price


    Click to enlarge
    JB HI Fi price for same product

    Reddit users have slammed Harvey Norman after one person pointed out the large price comparison Especially as JB Hi Fi is selling the same product for $49.

    ‘Harvey Norman just blows nuts. Their prices are obscene when you compare to JB Hi Fi, Good Guys, Appliances Online or pretty much any other electronics retailer I can think of. That’s local mobs too, not international GST exempt businesses,’ one user wrote.

    ‘Both myself and my wife have worked for Harvey Norman in the past. It is hilarious how much they hate JB Hi Fi and Officeworks, because anyone with half a brain will just ask HN to price match whatever is on their competitor’s website. They lose money hand over fist to those guys,’ a Reddit user wrote.

    Another said: ‘This is why we need GST on all overseas purchases, right Gerry, because that’s what’s hurting your local brick and mortars, right Gerry?’.
    Harvey Norman have made themselves available to comment for this story. 

    Qualcomm To Charge 80% A Smartphone In 35 Minutes

    As consumers demand better battery life and faster charging of their mobile devices vendors such as Samsung with their new Galaxy Note 5 have moved to delivering fast charging capability.

    Now Qualcomm who has been losing market share due to their processors overheating has moved to deliver fast charging as a standard feature in their processors.  

    The new Qualcomm technology is called Quick Charge 3.0, and it’s 38 per cent more efficient than its predecessor. Qualcomm claims it’ll take a mobile from zero battery to an 80 per cent charge in just 35 minutes, in comparison a current model smartphone without Quick Charge, takes about an hour and a half depending on the size of a battery. 

    Like Quick Charge 2.0, it’ll come built into Qualcomm’s Snapdragon processors, namely the Snapdragon 820.

    According to Qualcomm, a new Intelligent Negotiation for Optimum Voltage (INOV) algorithm lets your mobile “determine what power level to request at any point in time for optimum power transfer, while maximising efficiency”.

    Compared to Quick Charge 2.0, it will also reduce power dissipation by up to 45 per cent. Which should hopefully help our phones last longer in the first place.

    “We are significantly enhancing the capabilities and benefits offered by Quick Charge 3.0 to bring robust fast charging technology to all,” said Alex Katouzian, senior vice president, product management, Qualcomm Technologies, Inc.

    “Quick Charge 3.0 addresses a primary consumer challenge with today’s mobile devices in helping users restore battery life quickly and efficiently, and does so through leading technology and a robust ecosystem including leading device and accessory OEMs.”

    Quick charging tech is all well and good, but it’s really just something to tide us over until the problem of short battery life is solved. Whoever cracks that is going to make a fortune.

    Quick Charge 3.0 will be available in phones from next year. As well as the Snapdragon 820 chipset, it will be in the Snapdragon 620, 618, 617 and 430.

    Telstra Cuts $11B Deal With NBN Shares Set To Climb

    The Federal Government has thrown in the towel and signed an $11 billion dollar deal with Telstra that gives them access to the telecommunication carriers copper network, exchanges and other infrastructure needed to roll out the National Broadband Network’s fibre network across Australia.

    The Federal Government has thrown in the towel and signed an $11 billion dollar deal with Telstra that gives them access to the telecommunication carriers copper network, exchanges and other infrastructure needed to roll out the National Broadband Network’s fibre network across Australia.
    The deal announced by Prime Minister Kevin Rudd and Communications Minister Stephen Conroy, will see Telstra share climb tomorrow, as analysts realise that Telstra has achieved a deal that will benefit the carrier as they roll out new revenue services such as Telstra BigPond movies, TV content and new services for the connected home. 
    Under the deal, NBN Company will have access to Telstra’s network ducts, wires and infrastructure which will allow the NBN to roll out the fibre network quicker than without Telstra. 
    As part of the deal, Telstra will migrate their customers onto the fibre network. Telstra will receive $9 billion over 6 years to compensate for NBN Co using its infrastructure and the loss of future income from fixed-line customers. 
    According to the Sydney Morning Herald, a further $2 billion of government money will be used to set up a new company called USO Co, to look after Telstra’s Universal Service Obligations, retrain Telstra staff, and make NBN Co a wholesale supplier of fibre for new housing developments from January 1, next year.
    As part of the deal Telstra gets a number of regulatory concessions worth approximately $2 billion. They also get to keep their 50% in Foxtel and they will be allowed to bid for  4G spectrum as it becomes available. 
    The deal still has to be approved by the Australian Competition and Consumer Commissaion and if approved will deliver Telstra a post-tax net value of approximately $11 billion.
    Prime Minister Kevin Rudd said that negotiations with Telstra had been ”very difficult, tough, hard”, with some analysts claiming that Rudd did not want to go the next Federal Election fighting with Telstra over the NBN.
    More to follow.

    Sonos Takes Wireless Sound To New Level With Tuning Software + Expanded Play 5 Speaker

    Sonos the market leader in wireless audio has launched a brand new Sonos Play5 speaker which when paired with a second Play 5 and coupled with their new software delivers a pretty good sound experience, they have also delivered new Trueplay tuning software for Sonos speakers.

    The first thing you notice about the new Play5 is the shape, it’s a vastly improved design over the previous top end Sonos speaker. The new design can be mounted both vertically and horizontally. 

    And at $749 this product delivers excellent value for money.

    As for pricing Sonos earlier today dropped their overall pricing of all Sonos products by 10% which pretty much puts them on parity with US pricing for Sonos gear. 

    With the new Sonos Play5 it not what you see that delivers the rich wireless audio experience that Sonos has become famous for. 


    Click to enlarge


    It’s their software that differentiates this brand from a lot of other audio brands who are trying to break into the booming wireless audio market.

    Sonos Chief Product Officer Marc Whitten recently said that software is driving the next wave of audio innovation and it’s making music sound better than ever imaginable.

    He said in a recent blog “When file sharing exploded onto the Internet in the 90s, more than a century of recorded music was turned on its head.  Our relationship with music has changed dramatically in the twenty plus years of innovation since.

    “In the blink of an eye, the mixtape was replaced by the super-curated playlist, and social media superstars ushered in a new golden age of the live performance. The only constant has been change, and only one thing is sure – we are just getting started”.

    He added “Innovators like Spotify, Pandora, SoundCloud and now Apple have embraced the streaming revolution, offering everyone from the 12-year-old down the street to the deepest vinyl connoisseur access to the entire history of recorded music, powered by software. Digital music is, simply music”.

    Also released by Sonos is new speaker-tuning software called Trueplay, this software which can be accessed via the Sonos app allows a Play5 owner to tune their Sonos speaker to a room or ambient environment. 

    Both Trueplay and the new Sonos Play5 will go on sale at Australian stores shortly. 


    Click to enlarge


    With Trueplay which I saw demonstrated at a Sydney Hotel recently one is able to place a speaker in any position and then tune the music stream to delivers studio-quality listening experiences in each room of the home.

    The only problem at this stage is that the new Trueplay software is only available to Apple device owners which says a lot as to their commitment to the Android platform, which Sonos engineers believe delivers an inferior microphone capability. 

    This is despite products like the new Samsung Edge, Note 5 and the new HTC One M9 and LG G4 delivering excellent microphone recordings. 

    ” Using the Sonos app, the microphone on an iPhone or iPad, and a special tone emitted by the Sonos speaker, the system analyses how sound reflects off walls, furnishings, glass and other surfaces in any given room. Sonos then smartly tunes that speaker so the music sounds its very best” said Niv Novak from Sonos Australia.


    Click to enlarge


    “Our remit at Sonos has evolved from constantly working to improve the general sound of all of our speakers to customizing them: first by room, and in the future, by person, activity and content,” said record producer, composer, and Sonos sound experience leader Giles Martin. 

    The new Sonos Play 5 speaker has been redesigned with six synchronized, custom-designed drivers, the speaker’s three mid-woofers create smooth mids and deep, powerful lows, and three tweeters deliver crystal clear highs at any volume.

    The sound I heard from the new speaker was significantly superior to their current Play 5 speaker which was one of the first released by Sonos 10 years ago in Australia.

    When paired they delivered a true value for money experience that is up there with some serious high priced audio gear. 

     The new array produces a soundstage that is much wider than expected in a single speaker, creating room-filling sound with precise separation of vocals and instruments. 

    In addition to horizontal orientation as a standalone speaker, two speakers paired together vertically deliver stereo sound with a focused and intense sweet spot. 
     
    Paired horizontally, they create a larger stereo image for an immersive, room-filling listening experience.  
     
    A closer look at the facing grill mesh reveals 60,000 individually drilled holes in the grill.


    Click to enlarge


    Sonos has also delivered a total redesign of the touch controls on the top of the speaker. 

    Smart sensors make these touch controls responsive to all orientations, so the volume-up is always facing up.  All you have to do is stroke the controls manually or resort to your app to control the new speaker. 

    Breaking Sound Barriers

    Whitten said “For a truly exceptional result, hardware and software must work in harmony. Our new Play5 brings together all of our learning from many years of hardware and software design and iteration. Smart to its core, its dipole array delivers an incredibly wide soundstage. An accelerometer recognizes which of its three orientations it’s in and adjusts the tuning to suit your listening style” 

    He added “Sonos’ ultimate goal for sound is to reproduce in your living room what the artists created in the studio, in the purest way possible. That’s why we work with artists like Rick Rubin, Giles Martin, Q-Tip, and many others. 

    “We believe software is the ultimate craftsman’s tool for our speaker’s sound as well. From the early days of recording, speaker makers have been trying to reproduce the exact sound of the original musical performance in your home. The last 100 years of innovation in the loud speaker were driven by design, materials and manufacturing techniques-primarily hardware innovations and improvements”


    Click to enlarge



    “At Sonos we realized with our first speakers that by controlling the custom-designed drivers, we could ensure that no matter how high you turn up the volume, the music will never distort. We realized that by using software, we could improve how our speakers sounded in just minutes, not months. We learned how to control multiple drivers into an array that delivers a previously unfeasibly wide soundstage for home theatre, and how to create deep bass with just a tiny unit. But that really was just the beginning” 

    Trueplay
    He added “We are convinced that software can drive many more years of innovation in sound, making speakers sound better in any environment – smarter, more aware, and reactive to their environment. Which is why we’re so excited to be launching the first chapter of this innovation with Trueplay. It’s a major step in making sound itself smart.