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; } } Oonagh Reidy, Author at Smart Office - Page 78 of 116

    Smart Office

    News Ltd Bet $2bn Pay TV, Sports

    Murdoch empire pounces on Foxtel, Fox Sports by launching a takeover bid for ConsMedia.


    Click to enlarge
    Newspapers may be slumping but TV still fair game.

    Rupert Murdoch’s News is looking to get another 25% stake in Foxtel after making a $1.97bn bid for 100% of Consolidated Media Holdings, owner of stakes in both the Pay TV giant and Fox Sports.

    The deal, if approved, would mean News Ltd would own 50% of Foxtel, with Telstra owning the remaining 50%, and would also own 100% of Fox Sports.

    James Packer, who owns 50% of Consolidated Media Holdings (CMH), confirmed the $3.50 a share bid for the media holdings company.

    “Subject to this CPH, considering the offer price of $3.50 per share to be fair, will support the Proposal in the absence of a superior cash offer.”

    Packer is Exec Chairman of Consolidated Press Holdings (CPH), CMH’s biggest shareholder.

    The billionaire, who is focusing more on his casino interests of late, also said CPH welcomed News’ proposal and “looks forward to CMH and News working together to address the detailed terms and conditions.”

    Kerry Stokes, who owns just under 25% of CMH, has yet to make any soundings about the bid. Rumours circulated last month that Packer intended to ditch his share in Pay TV giant Foxtel, so the announcement is no surprise.

    CMH has yet to approve the deal, which is also subject to approval by competition watchdog, ACCC, and News board and shareholders.

    News Ltd said there is “no certainty any transaction will eventuate,” has to undertake due diligence and be satisfied that it will have no post completion obligations in respect of CMH liabilities.

    The CMH Board says it will engage with News in relation to the deal and thrash out the finer details.

     

    The deal, if approved, wont be completed until the final quarter of calendar 2012.

    Consolidated Media Holdings Limited (CMH) is a media investment company and invests in new media, according to its website.

    This takeover comes as traditional revenues streams from newspapers (Murdoch senior’s pet favourite) continue to slide, and after Fairfax’s announcement of almost 2000 lay-offs this week. News Ltd are said to announce their own restructure shortly.

    Betta Bucks Retail Slump

    That’s betta: 60% hike in income for the BSR group.

    The results for the BSR Group, owners of Betta Living franchise, for the year ending 31 March 2012, announced yesterday reflects the solidity of the business and the increased income returned to members,” Betta CEO Graeme Cunningham said.

    Cunningham also attributed the income lift to BSR’s alliance with buying group NARTA.

    “The additional benefits arising from the alliance with NARTA and the Group’s direct arrangement with supplier-partners are being promptly returned to members, resulting in the highest return of income since the Group’s inception in 2006,” he said.

    The return to retailers in rebates and direct costs including advertising and marketing also rose to 79.4% of total income, up almost 10% on a year ago.

    “The return to retailers also included additional retail support funds that were set aside to assist the store rebranding initiative from Betta Electrical to Betta Home Living.”

    However, Betta’s CEO also recognise the current “malaise” in the retail industry, which has seen several casualties, of late.

     

    “To compound the wider economic malaise, the electrical goods retailing industry has experienced continued price erosion in a number of key categories and seen a number of specialist retailers fail.”

    “When laid against this backdrop, the achievements of the Group and the growth achieved in the business, is all the more satisfying,” he said.

    Betta also recently added 40 Retravision stores to its network and with the recent additions “we are well positioned for the future,” Cunningham added.

    Forget ‘Surface’, Microsoft Smartphones On The Way?

    That’s according to one analyst who believes the software giant’s next move is to mobiles.


    Click to enlarge

    Microsoft may be already working on an own branded smartphones, believes analyst Rick Sherlund from Nomura who says industry sources have indicated operation smartphone may already be in action.

    The ‘sources’ indicated the giant may be working with a contract manufacturer on the new handset.

    “It is unclear to us whether this would be a reference platform or whether this may be a go-to-market Microsoft branded handset,” the analyst said in an investor note.

    Earlier this week, Microsoft surprised everyone by launched its own braded tablet device, a 10.6″ Windows 8-based tablet called “Surface”  with a USB 2.0 port, rear/front cameras, a built-in stand (on rear only 0.7mm thick), HDMI output, and a magnetic cover.

    Read: Microsoft Launch ARM + Intel Powered Ivy Bridge Tablet

    “Microsoft’s move emphasizes a change in the PC industry highlighting tighter integration between software and hardware,” another analyst, Richard Shim, from Display Search said this week.

    “Microsoft has joined a short but growing list of companies complementing their traditional businesses: Google with Motorola Mobility, HP with Palm and its webOS.”

     

    “The rate of success for these companies so far has been modest,” Shim said citing Apple as the best example of the value of controlling hardware and software.

    While the move is “positive” and will rise the level competition in the space, “it will likely be a slow build to significant influence for Microsoft in the tablet category,” Shim concludes.

    That’s Fast! Telstra Gateway To ‘Blistering’ 8Mbps Speed $89

    Telstra has just unleashed new hotspot that turns into portable Wi-Fi and Ethernet device proming ‘blistering’ sppeds of 1.1Mbps – 20Mbps. Ultimate Gateway allows businesses take a super-fast network with them on the go – a device that turns a single Telstra Mobile Broadband service into a portable Wi-Fi and Ethernet hotspot for multiple devices.


    Telstra Ultimate Gateway to broadband heaven

    Developed in Australia by NetComm, the nifty Telstra device operates on Next G delivering powerful Wi-Fi and wired network for multiple devices – 16 wireless and four wired  – and users, the telco said today.

    Gateway will provide “blistering” mobile download speeds from 1.1Mbps – 20Mbps in Telstra 3G Dual Channel HSPA+ enabled coverage areas, which covers 60 per cent of the country.
     
    In areas outside of this, including some metropolitan and regional areas, Ultimate Gateway provides speeds of 550kbps-8Mbps and 550kbps-3Mbps elsewhere.

    It supports fast Wi-Fi n connectivity (as well as Wi-Fi b and g) and comes with 2X built-in USB 2.0 ports that customer can use to share access to a printer or mass storage device.

    It is also first portable mobile broadband gateway in Australia to use dual channel HSPA+ technology and is double the speed of  the telco’s previous mobile gateways and sets up within minutes, say Telstra.

    Gateway integrates four internal antennas for stronger signal strength.

    So, how is this super gateway opened? It can be purchased for $0 upfront on the $89 Telstra Mobile Broadband Standard Plan over 24 months with 15GB included data to use in Australia (min cost $ 2,136) but can also be purchased outright for $399 from Telstra Business Centres and Stores.

     

    “Ultimate Gateway is the fastest mobile gateway device in Australia and allows businesses to connect teams or multiple Wi-Fi-enabled devices wherever there is Next G network coverage and power,” says Anthea Roberts, Director, Telstra Mobile Broadband,

    “This makes it incredibly simple to establish internet-powered networks at temporary office locations, conferences, tradeshows, construction sites or when on the road.”

     

    eHarmony: We’ve Been Hacked Too (+Last.fm)

    Dating website and Last.FM are the latest victims of hack attack.


    Click to enlarge

    This comes after professionals network LinkedIn confirmed millions of user passwords had been leaked on a Russian hacking forum database, which eHarmony now says included some of its members.

    “After investigating reports of compromised passwords, we have found that a small fraction of our user base has been affected,” eHarmony’s Becky Teraoka, wrote on a blog.

    And it appears British music site LastFM has also been affected by the hack and are “asking all our users to change their passwords immediately.”

    Reports indicate around 6.4 million LinkedIn passwords were leaked online, and SophosLabs reckon around 1.5 million eHarmony users details were leaked.

    The dating site said it is “continuing to investigate” but “as a precaution” has reset affected members passwords.

    Affected members will receive an email with instructions on how to reset their passwords.

    eHarmoney, which brands itself as “#1 Trusted Online Dating Site for Singles” has around 20 million registered online users.

    “The hashes of 1.5 million eHarmony passwords were uploaded to websites, where hackers were encouraged to join forces to crack them,” says Graham Cluley, Sophos Labs.

    The leaked passwords on the Russian hacking forum appear in the form of a cryptographic “hash” which converts text into a sequence of numbers and letters using a mathematical formula, say security experts.

    Read: LinkedIn Hack: 6.5 Million Passwords Spill Russia

    However, eHarmony’s Teraoka offered passwords tips including: “create a strong password of at least 8 characters, composed of lowercase and uppercase letters and numbers and a different passwords for each of the Internet sites you use.”

    However, Cluley was less impressed with the matchmakers advise:

     

    “What really disappoints me is that eHarmony misses an opportunity to tell its users explicitly that if they use the same password on other websites they must change their passwords there also.”

    Users shouldn’t use the same password on multiple websites such as Gmail, Facebook and “doing so is a recipe for disaster” he warns, as if one site is compromised all other online accounts with the same password could fall.

    Teraoka also assured its 20m members it uses robust security measures, including password hashing and data encryption, to protect members’ personal information.

    “We also protect our networks with state-of-the-art firewalls, load balancers, SSL and other sophisticated security approaches.”

    Users should also change their passwords every few months, she added.

    Hello Droid: Moto 4.3″ HD Bionic Specs Revealed

    Whats in a Droid touch? Well, a lot actually: 4.3-inch qHD display, dual core 1GHz OMAP4430 processor (as fast as the Atrix), 8Mp camera, 1080p video, 1GB RAM memory (higher than anticipated), and 4G compatible.


    Click to enlarge
    Touch down: Motorola Droid Bionic is on the way.

    And as we know already Droid Bionic XT 865 boasts Adobe Flash Player and HTML5, to boot and as the name suggests runs Android 2.3.4. 


    Motorola’s high spec Atrix smartphone, released here earlier this year, was subject to some criticism for its dated Froyo 2.2 OS, when other comparable releases in Australia like Samsung Galaxy S II were carrying Google’s updated Gingerbread 2.3. 

    However, it looks like Motorola has learnt its lesson. 

    These new specs, uncovered by Android Central on Moto’s dev site, show three new models are on the way: the aforementioned Droid Bionic XT 865, Droid 3 XT862 (sliding keyboard), and Photon 4G (also touchscreen). 

    However, whether these others will hit Aussie shores is questionable.

    Droid Bionic appears to be the one to watch, in any case, with Moto fans eagerly awaiting some new blood, and is due for release first in the US in September.

    Last month, Smarthouse reported how the Atrix 4G was being pulled from shelves by AT&T with a notifcation saying: “new handset coming soon.”



    JB Hi-Fi: Why We’re Up

    JB HiFi’s sales lift defied the odds, as major names like Myer and Harvey Norman report a slump, bemoaning price deflation, margin squeeze and anaemic consumer spending.


    Click to enlarge

    So, what’s their secret?

    Their low cost model enabled the operation to manage “abnormal levels of price deflation and competitor discounting whilst continuing to trade very profitably,” says CEO Terry Smart.

    Even JB results have shown the selling price of TVs fell 24% in the past year, something Gerry Harvey also referred to last week, as Harvey’s 39% profit slump was announced.

    But the retail environment is faced with both “cyclical and structural challenges”, the retailer admitted, as it announced a sales rise of 7% for the full year ending June 30 and net profit decline of 5% on FY11.

    Read: JB HiFi Sales Soar 7%

    Despite the issues pervading the industry at present, “not all retailers will be impacted by these challenges to the same degree. Some will benefit as the strong get stronger and less efficient retailers close.”

    The ‘pack ’em high and sell ’em low’ strategy appears to be paying off – JB Hi-Fi has the lowest cost of doing business of any retailer – at 14.9% – even compared to Best Buy: 20.4%, the largest CE retailer in the world and Amazon’s 20.65% CODB.

    A JB Hi-Fi store is 2.5x smaller than a traditional Best Buy store.

    “We have maintained a focused model with a very clear customer proposition – we are leveraging this to grow additional online and new digital sales.”

    But CE categories are not as exposed as other retail sectors like apparel, JB notes in its investor presentation FY2012:

    “We believe “The JB Hi-Fi Model” is best positioned to maximise the outcomes in this sector positive customer experience.”

    JB also says its other secret weapon is its reputation as a loud and proud low cost retailer alluding to its “very distinctive brand personality” unlike CE rivals.

    JB is also perceived as a young brand with the latest gear, unlike stuffy rivals like Dick Smith and Harvey’s.

    Dick Smith and Retravision Southern are just two of the CE casualties of late, although an announcement on the sale of the former is set to be announced this week, with a buyer believed to have been found for the struggling Woolies-owned chain.

    The yellow retailer will be able to pick up increased share as the difficult trading environment forces higher cost retailers to go under, it said today, something JB boss Terry Smart referred to in an interview with Channel News last week.

    JB says it has led the CE market on lower pricing with its cameras and gaming factory scoops and direct imports; however, online rivals continue to emerge, Smart told CN last week, even as bricks and mortar goes into consolidation

     

    JB also say it is well accustomed to changing in line with consumer trends, noting the decline of categories such as car audio, speakers and component stereos, which once accounted for large proportion of sales. (Let’s not forget JB started out as a HiFi retailer).

    “We managed the decline of these categories in line with customer demand as we will do with our current software categories,” the presentation states.

    The business remains highly profitable with only 2 out of 129 stores not turning a profit in FY12.

    In its press statement JB also stated its online operation was driving sales, with over 927,000 visitors weekly to its site, making its one of OZ’s most visited sites.

    However, direct online sales account for just 1.6% of total sales, at present. Its JB HiFi Now music streaming service will also drive web sales and growth by “leveraging its entertainment heritage” in FY13.

    It also mentioned new digital services soon coming but JB’s marketing Manager Scott Browning refused to comment any further when quizzed by Channel News. It is also going live with a span new website, though there’s no timeline on this.

    Its store location strategy has always been about positioning the stores in high foot traffic precincts, which also maximise “additional impulse sales”

    Gerry Harvey take note.

    The Web “Spin And Bull****” Says Gerry Harvey

    A load of old rubbish: That is Gerry Harvey’s assessment of his company’s omni-channel strategy, as he rejects online retailing
    This is in stark contrast to Harvey Norman’s Chairman comments released along with the retailers FY12 results on Friday , in which Mr Harvey hailed its new omni-channel strategy as “backbone of its business” and said it had made “strong progress throughout the year”

    In an interview with Business Day on Friday, 73-year old Harvey Norman Chairman, Gerry Harvey, labelled his omni-channel strategy including digital, in-store and mobile components as a load of “spin and bullshit.”

    “You devote all this time to your omni-channel and integrated bloody … and you go on with all this bullshit and the result is that it is 1 per cent of your sales. But if you don’t go on with the bullshit you are out of fashion, you are not with-it.”

    “I am reluctant to do it but I do it, because if I don’t they label me a dinosaur.”

    On the omni-channel speel, which every major retailer from Myer to Officeworks is shouting about of late, Harvey added: “You have to have it, the problem is that as a public company you have got to give the spin, so every company is out there giving the spin and we do the same thing.”

    On Friday, Harvey Norman announced a massive 31.6% slump in net profit after tax to $172.47 million for the year ending 30 June 2012.

    Harvey FY 12 sales slumped 8% FY12 with every quarter, bar the first, showing a fall of almost 10%.

    Harvey blamed “a glut of products sold at never before seen prices” flooding the market following the demise of retailers WOW Sight and Sound, Retravision and Dick Smith’s restructure, for the tumble in profits.

    This could be another part of Mr Harvey’s “spin” as the CEO of JB HiFi, Terry Smart, said his company, whose net profit fell 5% to $104.6m this year, had not been impacted nearly as much by stock dumped into the market, as claimed by Harvey Norman.

     

    “And everyone does it,” Mr Harvey said, “but then when you check with Myer or David Jones, whoever, JB Hi-Fi, Good Guys, its nothing of their sales, somewhere between half and one-and-half per cent.”

    JB Hi-Fi recently stated its hugely successful online operation was driving sales and almost 1 million visitors trawl its website every week. Online sales accounts for 1.6% of total sales at JBs.

    However, Alan Oster, NAB’s Group Chief Economist begs to differ with Harvey’s negative sentiments on the web, saying the  channel is just going to keep growing stronger and stronger every year.

    NAB’s Online Retail Sales Index relased on Friday, showed online sales are growing over 20% annually, and although from a small base, is increasing five times faster than bricks and mortar retail.

    Traditional retail sales were worth $220 billion for the 12 months ending June 2012, while online retail spending hit $11.7 bn.

    “Online is still a new channel but it’s an important and growing one and although accounts for 5.3% of traditional bricks and mortar sales, in five years time this will be 10%.”

    And Oster doesn’t see it slowing down any time soon either, adding retailers have no choice but to go online.

    Big name retailers like Myer, and David Jones adopted an onmi-channel strategy as “they realised their lunch is being eaten if they didn’t”.

    “However, in consumer electronics sector, whether you’re online or not it’s still tough at the moment,” he adds. 

    There are also different demographics for traditional retail and online consumers. NAB’s Index shows fashion and department stores are the biggest online sales category, with 30 and 40 year olds among the biggest spenders while in traditional retail, food is the always largest category.

    JB Hi-Fi iPhone 4S High Hopes As Sales Slip

    Its not only telco’s who hope iPhone 4S will pull it into the black. JB Hi-Fi too are hoping iPhone 4S, which goes on sale this Friday, will also give it a much needed sales push.


    Click to enlarge

    This come as CEO Terry Smart revealed at its AGM today that recent trading conditions, in three months to September, have been challenging, with comparable sales down 3.5% on the same period 2010.

    However, total sales actually rose 6.6%, Smart revealed.

    However, the yellow discounter is hopeful for good times ahead in particular during the usually busy Christmas period, thanks to  a certain new Apple iPhone, which has already sold one million worldwide on pre-order.

    And the retailer appears to be taking no chances on sales of the cult smartphone, already putting a centre iPhone 4S banner on its website homepage, flogging the device as “amazing” and calling on consumers to “pick up where amazing left off.” 

     “As with previous years, we are well positioned to take advantage of the important Christmas trading period due to our large assortment of gift giving merchandise,” Smart assured investors.

    And it expects that the imminent launch of the iPhone 4S, out this Friday, and other new movie and computer releases will boost flagging sales.

     

    “The launch of the iPhone 4S, solid music, movies and games releases and continued growth from the computers and accessory categories will assist sales in this quarter,” he added, according to AAP.

    “Whilst we expect underlying trading to remain subdued, we are still optimistic the Christmas trading period will be a successful one.”

    But its not just Christmas plans concerning the retailer – Smart is also looking at long term growth and says it hopes to acquire 13 to 15 store sites every year for the next 4-5 years.

    JB’s offer phones on Telstra network, who have just revealed their iPhone 4S plans.

    Read ‘Secret Is Out’: Coy Telstra Kick Off iPhone 4S

    Telstra: NBN Today, (Libs) Plan B Tomorrow?

    Today Labor, tomorrow Libs? Telstra say they are ready for a change of guard in the Federal government, if say, Tony Abbott slips into Julia Gillard’s shoes any time soon.


    Click to enlarge

    .

    Telstra boss David Thodey says his telco was unfazed by a change in government and thus possible abandonment of the $36 billion National Broadband Network project, or alteration of broadband policy, he told the Queensland Media Club in Brisbane yesterday, reports AFR. 

    The breakup of Telstra’s retail and wholesale arms also recently received the go ahead as part of Labor’s NBN policy framework, which will see the telco receiving $11 billion payoff from NBN Co for its pits, ducs and copper network.

    “Should there be a change – be it technological or political – we think we’ve got enough safeguards there,” Thodey said. “I’m very confident we have enough protection in our contracts to realise the value we’re talking about.”

    Thodey also dampened expectations surrounding the opportunities a high speed fibre network will bring, saying Labor’s epectations are “inspired” but may not materialise to the extent it hopes.

    “I would say a large percentage of big business actually don’t deliver what we expect them to.So in that context, when we speak about the NBN, you know a $40-50 billion investment, that I think is quite actually inspired, in terms of what the government is trying to do,” he told the Queensland Media Club.

    “It is not going to be about the roll-out of fibre to 93 per cent of homes which will be the issue. It is what the hell we do with it,” he added.

    Mr Thodey is also ” not yet convinced that there is enough focus on the innovation and the different ecosystems that we need to put around that, even from educating people about how to use it.”

    Telstra has previously aired their NBN alternative plan which include a mix: “some fibre to the premises in some circumstances; we would use fibre to the node in some circumstances; and we would continue broadband over copper in some circumstances,” former CFO John Stanhope said last year.

    The telco would “provide high-speed broadband in a different way. We would have a least-cost, blended technology approach.”

    The Liberal Party has made no secret of its disdain for the costly $36bn fibre broadband network the Labour government proposed during the Kevin ’07 campaign.

     

    Malcolm Turnbull, the Liberal Communications shadow minister, is one of the most vociferous opponent of the NBN project and has proposed a mix of wireless, fixed line services as a less costly NBN alternative, previously declaring Telstra was in a ”prime position” to build a considerable fibre network of its own.

    Since by 2013 if Labor are booted out of power, the NBN will be partly built, Turnbull is proposing a sell off of fibre back to telcos.