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 66 of 116

    Smart Office

    Fetchtv Flogs Cantonese Packs $49

    Fetch are reaching out to 244,000 Cantonese speakers in Australia with launch of new TVB pack.


    Click to enlarge

    244,000 Cantonese speakers in Oz are now being offered five of the “most popular” channels from TVB, one of Hong Kong’s largest broadcasters, including TVB Jade, Junior, Xing He and Lifestyle.

    Fetch’s IPTV Cantonese pack costs $49.95 per month, plus basic subscription which starts at $9.95 and the five channels available include:

    TVB Jade (TVBJ), boasts Hong Kong latest drama series, news and information from mainland China and Hong Kong and access to Australian shows including Australian News, Public Forum, Today Tonight, Finance Caf_ and Smart Guide.

    TVBN is another current affairs station in the pack showing 24/7 news and current affairs from Hong Kong and around the globe.

    TVB Junior for young viewers and parents, screening Cantonese language learning programs, general knowledge, cartoon, and dramas series, while Xing He – the world’s first Chinese TV drama channel showing TVB’s classic drama series.

    TVB Lifestyle screens shows hosted by celebs and other professionals on health, travel, food and fashion trends in China and abroad.

    Because FetchTV is delivered over broadband from ISP’s Optus, Internode, iiNet, Adam Internet, Westnet, Netspace and MyTelecom, the new service can be accessed by those in apartments and rented accommodation.

    Programming is also unmetered, meaning it doesn’t count towards internet downloading quotas.

    The FetchTV IPTV service includes: a 1 terabyte personal video recorder to record live TV, access to on-demand new-release movies, and other content from providers.

    Read: Haa! Shi! Optus FetchTV Invades Asia Here

    Last month, Optus announced expansion of its fetchTV IPTV service, MeTV, introducing 51 new foreign language channels and 4 world packages – offering Hindi,  Chinese Mandarin, Korean, Mandarin (Singaporean / Taiwanese) programs.

    iiNet also has several Chinese and Mandarin world packages for Asian audiences as well as others from India, South Korea and Hong Kong.

    The Cantonese channels are live on all ISP services, except Optus, which will commence later this month, Fetchtv confirmed.

    “The TVB Cantonese pack offers the very best of programming for Australia’s large Cantonese speaking population. The TVB channels are extremely popular in Australia, but this is the first time they have been available without a satellite, making it an ideal choice for customers living in multi-dwelling units, or for those customers looking to combine the very best of Cantonese and English programming,” said Scott Lorson,  FetchTV CEO.

     

    “TVB is pleased to partner with FetchTV in making our Cantonese channels available to those Australian households who are not currently able to access them,” said Patrick Wong of TVB.

    Together, TVB & FetchTV will now be able to connect even more Australian Cantonese speakers with home. ”

    Gerry Harvey Gets Gloomy, Predicts Mass Closures

    Baton down the hatches: Harvey Norman boss is predicting dire times ahead for retail.


    As Harvey Norman owned Clive Peeters and Rick Hart outlets run aground, Gerry Harvey predicts similar fate for other electronics businesses. 

     “There’s no doubt in the world in the next six months many (electronic retailers) will have to close. It’s just a matter of how many,” he said in an interview this week. 
     Times are a-changing, the 71 year old admits. 
    “Every year in my life I’ve said this is going to be the best one ever”, confessing he no longer holds this sunny outlook, predicting a dismal sales period in H2 for Aussie retail. 
     However, there was some light at the end of the tunnel – called iPad 2, which was “selling like crazy” in store, he said, with Android’s playing catch up. 
    But laptops and flat screens were delivering less joy to the bottom line – while volumes are up, profit margins and revenue are tightening.  
    However, the retailer did recently report growth in white goods, cooking, home appliances and floor care. 
    He also said competition from online traders was “negligible”, which is interesting, considering only yesterday the Harvey Norman Chairman confirmed his giant was entering online world of selling next month. 
    “Eighty percent of our retail products will be online in September,” he confirmed yesterday. 
    “I’m sick of talking about online [retail] to journalists” a fed up Harvey added. 
     

    These latest comments suggest Harvey is still in denial about the elephant in the room that is online trading (or lack of) and the huge impact it has had on his bricks and mortar business. 

    Gerry Harvey’s previous refusal to sell goods on its website may be one of the reasons behind its dull sales figures – posting a 3.6% fall in its most recent results last week. 
    Global sales for the year to June 30th were $6.18 billion, with the retailer saying revenue had fallen in its franchisees due to a challenging operating environment, a stronger Australian dollar and falling TV prices. 
    And this is part of an ongoing southward profit trend at the retailer.  
    In its previous results to December 31 profits slid 16.5% to $198.61m (before tax) – from high of $237.77m, in spite of having increased its store numbers from 195 to 198. 
    Harvey’s have now moved to close seven Clive Peeters and Rick Hart group stores due to poor sales following its acquisition last year for $55M, announced last week. 
     In comparison, Harvey’s biggest rival and darlings of the web, JB Hi Fi, announced a 13.3% rise in profits and an 8.3% rise in sales.  
    And ditto for Dick Smith, who also have a massive online presence, reporting a 7.1% jump in comparable sales for the full year to June 26, which it attributed to “the refreshed online store.”

     

    Doesn’t that tell you something, Gerry? 

    Harvey Norman ‘Sorry’ Over $5 Fiasco

    Retailer left red faced after sending dodgy emails and $5 vouchers to consumers
    Consumers late last week received emails (and a $5 voucher) for subscribing to Harvey Norman’s VIP electronic newsletter – even though they had not signed up to receive it.

    Harvey Norman is currently looking to encourage consumers to sign up to his newsletter, offering a carrot in the form of a $5 voucher.

    Harvey Norman quickly issued an apology email and invitation reissue saying:

    “We apologise for our earlier email.

    “Sorry, we accidentally sent you a welcome email today. We realise that you haven’t subscribed yet and want to reassure you that we will not continue to email you in the future unless you opt-in.

    “We take email opt-in requirements very seriously and are sorry for our mistake.”

    The retailer reissued the VIP invitation email saying: “we hope you will sign up and take advantage of your $5 voucher.”
    However, consumers need to spend $25 in or more to avail of the credit.

    It is not known where the retailer got the email addresses from but it appears the action could be violating the Spam Act 2003 which forbids unsolicited emails and those recieved without consent.

     

    The email also asks consumers for their name, email and postcode, and claims it is ‘easy to unsubscribe’ according to the invitation to sign up, which is a legal requirment under the Spam Act.

    Singing up to Harvey Norman VIP email offers “Great competitions and VIP invitations” ‘Exclusive product offers and VIP events’, the company says.

    Retailer Fined $10K For Text Sacking

    A text is no way to finish it with anyone.


    Click to enlarge

    That’s what Fair Work Australia ruled yesterday against a boutique owner in Sydney’s south west who sent a text to a worker on Boxing Day seeking to terminate an employment arrangement.

    “You can pick up your pay tomorrow and drop the key. You don’t need to call me and I don’t see that we can work together,” Modestie Boutique director Sophia Sarkis wrote to a member of staff after a disagreement over work schedules.

    Sarkis also wrote to her employee that she was “hurt” by her employee, Sedina Sokolovic actions, who sought to change a shift without prior notice, News Ltd reports.

    Fair Work  ruled the action was harsh, unjust and unreasonable and awarded Sokolovic almost $10,000 ($9992).

    NBN A Monopoly Waiting To Happen Warns Rudd Boffin As Telstra Roadshow Revs Up

    The NBN is anti competitive and a monopoly waiting to happen, Kevin Rudd’s right hand man has warned the ACCC.


    Click to enlarge

    Consumer interests are also neglected by the new telco monopoly, and will result in “higher prices, less choice and innovation,” the ACC have also been warned.

    This startling new attack on the $36bn national broadband plan was voiced by former University of Melbourne Professor Gans, a leading advisor to former PM Rudd, in a report co-authored by MIT professor, Jerry Hausman.

    The report, submitted to ACCC this week relates to the consumer and competition body’s ongoing investigation into Telstra’s proposed structural separation and ‘migration’ plan.

    Key elements of the NBN Co, the company charged with fibre optic roll-out, will herald a “return to monopoly network provision in telecommunications in Australia,” namely former state owned Telstra, but by a different government agency.

    The main point irking the professors is the acute lack of competition the NBN Co regime would usher in, disallowing main player Optus and Telstra to compete in fixed line services nor will they promote  wireless broadband solutions against NBN services.

    “The stated goal of this provision is to make NBN Co. (a government–owned entity operating as a business) the monopoly wholesale provider of fixed line services in Australia; at least with respect to existing incumbents in that industry” the report dated September 13 states.

    “Specifically, they are designed to prevent the two largest incumbents fromcompeting upstream against NBN Co in the future.”

    “Our conclusion is that the Telstra Structural Separation Undertaking and Draft Migration Plan is likely to be massively anti—competitive. While it may suit the needs of the Government,Telstra,and others,it will lead to significant consumer harm lasting for 20 years or more.”

    “No one at the bargaining table appears to have represented consumer interests,” the submission also warns.

    “While we both reside outside of Australia,we were motivated to provide a submission because of our long history and experience of working on telecommunications and competition policy issues there,” say the duo, who have called on the ACCC to “step in” and protect interests, against a turn to monopoly network provision in Australia.

    The appropriate solutions would be (for Telstra) to divest itself of its hybrid fibre (HFC) network to an “independent firm, perhaps Foxtel” and for that firm to supply broadband and phone services “independently. “

     

    This latest warnings come as Telstra begins its own rollout of the NBN roadshow – its bid to convince shareholders to give the green light to approve the $11 billion deal with the broadband company it is looking to get signed off.

    In what the Australian Financial Review called “the biggest series of retail shareholder briefings since the 2006 T3 privatisation” David Thodey’s telco is on a mission to get the lucrative deal “worth billions” the go ahead.

    Facebook ‘Feeds’ Invade: Friends + Photos + Music

    Forget one newsfeed – Facebook is now bombarding us with multiples – All Friends, Photos, Music AND Following.

    Yes folks, the network with 1 billion members is taking the “clutter” out of its newsfeed by giving us more content to “drill into.”

    Newsfeeds should have “high-quality public content and socially relevant content, and to drill into any topic,” Facebook said as it announced it makeover of its biggest social tool.

    Wow.

    Here’s a breakdown of the four:

    – All Friends – everything your friends are sharing/doing

    – Photos – Friends photos and Pages you like

    – Music – posts about the music you listen to

    – Following – latest news from ‘Liked’ Pages, media, people/companies you follow.

    The change was announced at a press event in Facebook HQ in Menlo Park, California, overnight.

    “We want more than a single feed of content,” said Mark Zuckerberg.

    Facebook’s hooded CEO also went on to compare the social network feeds to a ‘newspaper’: “We want to give everyone in the world the best newspaper we can,” Zucks declared.

    All posted media articles will include the logos of publishers, and Facie is also giving third party apps like Pinterest more prominence.

    Half of the social networks’ newsfeed posts are now photos and 25% are public post.

    The music newsfeed in particular will appeal to Gen Y – the primary Facebook user – and will feature ‘tons’ of music, says Zuck & Co.

    But hold on, the Newsfeed will still be the main event but info will be more honed down than previously, and gives you “Rich stories, visual and engaging. Your choice of different feeds, dig into any topic that you want. And a mobile inspired consistent UI” says Zucks.

    The cleaner feeds, which now have the same look as per Facebooks’ iPhone app and will be the same across tablets and PCs, is a clear nod to the mobile Facebook user.

    The changes have been likened to rival Google+.

     

    And if you’re thinking more newsfeed, more ads, you could be right although The Social Network hopes ads will be more relevant than prior.

    “We’ve completely rebuilt each story to be much more vibrant and colorful and highlight the content that your friends are sharing,” the network said in a statement

    “Photos, news articles, maps and events all look brighter and more beautiful.”

    The changes are being rolled out from today, but if you’d like to get it early, visit www.facebook.com/newsfeed and add yourself to the waiting list.

    The design updates will be available on the iPhone and iPad in the coming weeks and to Android soon after.

    Len Wallis On Sound, Big TVs And Why Foxtel Better Watch Out

    Len Wallis a leader in the specialist AV market is restructuring in an effort to better engage with customers.

    The Sydney based AV specialist is undertaking a major refit to better engage with his customers who spend up to a million dollars on AV and automation fitouts. 

    Len Wallis’ showroom at Sydney’s Lane Cove already has 10 sound-lounge/theatres, a virtual smart-house and an on-site service centre.

    But in a time when bricks-and-mortar stores is in decline and facing economic uncertainty, why is Wallis developing his state of the art showroom even further?

    The “substantial drop in price points of AV equipment” is driving traffic into his store, owner Len Wallis admits, but is also the result of “genuine increased interest in better quality by the consumer,” he says.


    Click to enlarge

    Demonstrating the gear in the connected environment is also critical, says Wallis, as is a WiFi network.

    Having an in-house wireless network is “absolute and we developed our system over time,” he says but “we’re pulling it out and have just installed a really stable network.”

    “Its got to be done” he admits.

    So what are the big in the AV business at the moment?

    Security is a big issue for custom gear but from Len Wallis’ point of view, entertainment is the main one.

    “The changes in Australia over six months are going to change everything as far as content provision goes ..the days of listening to music the way we used to are totally dead now. 

    “Entertainment is now about the sheer amount of content available and the ease of getting it as you can stream content straight to your system rather than messing around with iPods and downloads.”

    This means content players of old may be in for a shock.

    Traditional content providers including the likes of Pay TV giant Foxtel “is going to have to be very careful that a video version of Mogg suddenly appears on their doorstep and then they will have real problems.”

    There are a few players working on delivery at the moment so, watch this space, he says.

    The likes of Google, Apple and Microsoft are “perfectly positioned” to enter this market and Wallis reckons it’s where content is going to go.

    “Foxtel is going to have to pick a side and partner up with someone,” if they are going to make it in the new content provision era of cloud, streaming and IPTV.

    Quality of devices is also improving in general, with most now starting at 320Kbps instead of 128Kbps and there’s now an upgrade path available on audio files, but the sheer amount of content available is the big thing.

    Quality of projectors has also jumped and customer feedback has been extremely positive, Wallis adds.

    In the custom space, the AV guru says there is still traction in non-dedicated theatre systems which is going away from the general trend.

     

    Sonos is still “enormous..it’s everywhere and people are still going to multiple boxes as its simply, cheaper and easier to handle.”

    The issue of service provision is also on the horizon for Wallis but there’s an interesting twist on it.

    “Some customers are looking to us as service companies to install and program devices they bought at somewhere cheaper like Bing Lee.

    “They’re looking at us to do the service on it as they perceive someone else as cheaper.

    “However, we’re not geared up as a service company of that nature just yet..but it’s certainly something we are going to look at”, he says.

    So what about the TV market?


    Click to enlarge

    Wallis cites Panasonic VT series 65″ Plasma TV which he says is “unbelievable..a stunning screen” models but availability is scant on the Australian market.

    However, you “cannot get a Panasonic screen for love nor money in Oz at the moment,” he says.

    This supply side issue is due to Pana’s local entity not ordering enough, despite the company saying otherwise.

    “I would have sold 30-40 on orders [if the stock was there], which for us is a lot as we’re not a screen company. “

     And is the demand for larger screens like 75″ or 80″ growing as prices fall and bigger OLED screens emerge?

    “We don’t stock anything above 65″ in the shop, but we’re certainly looking at it,” Wallis says.

    “We’re being very cautions of who were dealing with at the moment,” he tells ChannelNews.

    “We’re selling screens but not making any money out of them in some cases,” resounding the battle cry on every retailers lips at present.

    The old reliables of pricing and margin squeeze are still a big problem, and customers are still not sure about spending which is a “big issue,” Wallis says.

    “But those customers who are spending are bringing their expectations down,” he notes. 

    Dick Smith Drags Woolies Down

    Woolworths feels the pinch as consumer electronics continues to slide.


    Click to enlarge

    Woolworth’s latest H2 2011 results, just announced, reveal net profit fall of 16.8% due to discontinued operations and $300 million provison it was forced to make for the divesting of Dick Smith, it said today.

    In January last, Woolies said it would sell off its Dick Smith electronics operation, and would close over 25% of its stores immediately here and New Zealand due to poor performance.

    As a result of the divestment, Consumer Electronics division has been disclosed as a ‘discontinued operation’, forcing it to make $300m provision and dragging earnings for H2 down for the Woolworth’s group.

    Dick Smith sales increased just 0.7% for the half year to December, a disappointing result in what should be one of its busiest trading period including Christmas, although comparable store sales did increase 2.4%.

    However, Woolies said sales for second quarter were “pleasing” given that consumer electronics continues to be impacted by the poor trading environment, price competition and price deflation in key products.

    The new format DS stores, which now account for 74% of all stores also continue to outperform older format stores and enjoyed sales growth of 8.7%, which could read as a sales pitch to any potential buyer.

    The process of getting rid of its electonics business is now underway and a “number of potential purchasers have expressed interest,” Woolworths Group said today.

    Read: Woolies Give Dick Smith The Boot

    Apart from electronics, Woolies’ other businesses are booming as total sales grew 5.2% to $ 28.9bn. Food and Liquor sales for the half-year were $19.6 billion – an increase of 4.3% on last year.

    Earnings before tax grew 4.1% from continuing operations (total Group EBITDA before Consumer Electronics $300m provision was up 3.9%).

    Net profits from Woolworths’ ‘continuing’ group operations including food, liquor and home improvement divisions which also grew to $1.1bn – up 3.1%, before consumer electronics was taken into account, the supermarket giant said.

    Consumer Electronics’ New Zealand sales increased 2.2% for the half year and comparable sales increased 6.5%, which it said was “strong” given New Zealand challenging macroeconomic environment and significant price deflation.

    Sales for BIG W, however, fell 1.3% for the half year to $2.4 billion. Comparable sales fell 2.8%, compared to a decrease of  4.2% in H1.

    Customer numbers and items sold increased during the second quarter (Oct-Dec period) although price deflation continued, averaging 5% and was most evident in Home Entertainment and Toys categories, the giant confirmed.

    “Trading over the Christmas was pleasing with positive customer and unit growth in December offset by deflation resulting in lower average basket sizes.” 

    Strong results were achieved in DVDs, Books, Toys, Sporting categories whilst cooler weather in December had a “modest” effect on apparel and outdoor categories.

     

    Earnings at the discount store fell 4.3% to $119.6m, however Woollies said the result for the second quarter was pleasing and showed positive growth, compared to 2010.

    “Woolworths Limited today reported an increase in net profit after tax from continuing operations of 3.2%. It was also pleasing to achieve an increase in our trading result of 5.6% before central overheads and our investment in Home Improvement,” said Woolworths Chief Executive Officer, Grant O’Brien.

    “This is a sound result considering subdued consumer confidence,deflationary pressures and the significant investment we are making in the business in line with ourstrategic priorities for growth.”

    Woolies also announced a 3.5% increase in fully franked interim dividend to 59 cents per share.

    Currently there are 386 Dick Smith stores between Australia and New Zealand. In January Woolworths will close up to 100 of these stores before the sale over two years.

    Robbers Nick $12M JB Hi-Fi Goodies

    Millions of electronic stock has been stolen from JB Hi-Fi stores in the last year, it emerged this week.


    Click to enlarge

    JB noted “increased shrinkage levels” or theft of over $12 million of stock from JB stores in the past year, which it said impacted on gross margin by 13 bps.

    This marks an increase in theft by $4m since last year.

    3% of the total Aussie retail turnover of $240 billion is attributed to theft, according to Australian Retailers Association and its on the rise, ARA Exec Director told Channel News.

    Headphones and DVD’s  were said to be among the items popular with thieves, scanning the aisles of JB’s stores.

    JB Hi-FI blamed it on staff roster changes and less staff on the floor as it looks to drive productivity levels.

    However, a large number of JB stores are located specifically in high footfall areas – like Westfield on Pitt St, Sydney and three stores on Elizabeth St, in Melbourne CBD- meaning its store traffic is huge, particulalrly in larger stores.

    JB were not available for comment at the time of writing.

    “Management conceded that an ongoing side-effect of driving productivity from existing staff (roster changes) was higher levels of stock shrinkage (theft),” RBS Equities analyst Daniel Broeren said in a research note.

    “Comments on the call suggested less staff on the sales floor, particularly during quiet trading periods, had contributed to the $4 million increase in shrinkage, which in turn had reduced the gross profit margin by 13 basis points in FY12

     

    “In our view, total annual shrinkage of $12.2 million (FY12) is now a significant detractor from EBIT. We note the roster changes came into effect in 2H12, and hence higher year-on-year shrinkage costs could be seen in 1H13.

    “In store productivity improved as labour rosters were driven to align with individual store sales.”

    Theft costs retailers more than $7.5 billion each year, according to ARA and is highest in NSW ($2.3 bn), VIC ($1.9 bn) QLD ($1.5 bn) and WA ($845.7m).

    Aussie Facebook Porn Troll To Walk Free

    A Queenslander convicted of publishing child porn images on the social network has won an appeal against his 12 month jail sentence.Bradley Paul Hampson, 29, who posted disturbing messages on tribute pages of two murdered Queensland children including “Woot I’m Dead”and “Had It Coming,” will now be free by September after having his sentence reduced to 6 months, Brisbane Court of Appeal ruled today.

    The self confessed troll will be on probation for a further two years, reports Fairfax media.

    Hampson, acting under a pseudonym of ‘Dale Angerer,’ was convicted of ‘internet trolling’ by the Court and sentenced on March 25 to a three year jail term by Judge Kerry O’Brien,  who ordered his release after  a year. Hampson’s lawyers argued the sentence was excessive.

    Trolling refers to a practice which refers to posting inflammatory or extraneous material online.

    The deceased minors were a 12 year old boy who was stabbed in a Brisbane school and a 9 year old girl abducted and murdered in February 2010 and posted images of one victim with a penis drawn near their mouth.

    When police raided his home earlier this year they found more than 200 images of children including those of missing Briton Madeline McCann and child murder victim Jamie Bulger with similar superimposed images loaded onto his PC at his Tarragindi home.