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; } } Merchandising Archives - Smart Office https://smartoffice.com.au/category/merchandising/ Thu, 18 Sep 2025 05:51:36 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 Amazon Unveils AI Tool That Builds Entire Ad Campaigns For Merchants https://smartoffice.com.au/amazon-unveils-ai-tool-that-builds-entire-ad-campaigns-for-merchants/ https://smartoffice.com.au/amazon-unveils-ai-tool-that-builds-entire-ad-campaigns-for-merchants/#respond Thu, 18 Sep 2025 05:51:36 +0000 https://smartoffice.com.au/?p=98821 Amazon has unveiled sweeping upgrades to its Seller Assistant platform, giving third-party merchants the ability to hand over entire advertising campaigns to AI. Sellers can now describe an ad concept in plain text and let Amazon’s chatbot generate taglines, images, scripts, music, and even full video storyboards. Amazon says the system reduces costs and speeds ... Read more

    The post Amazon Unveils AI Tool That Builds Entire Ad Campaigns For Merchants appeared first on Smart Office.

    ]]>
    Amazon has unveiled sweeping upgrades to its Seller Assistant platform, giving third-party merchants the ability to hand over entire advertising campaigns to AI.

    Sellers can now describe an ad concept in plain text and let Amazon’s chatbot generate taglines, images, scripts, music, and even full video storyboards.

    Amazon says the system reduces costs and speeds up production, turning what once took weeks into hours.

    Ads can then be distributed not only across Amazon’s own marketplace, but also on platforms like Prime Video, Twitch, Netflix, Roku and Disney+.

    The move raises questions about how AI will reshape digital advertising and small business operations worldwide.

    The tools are powered by Amazon’s Nova AI model alongside Anthropic’s Claude and are integrated with Creative Studio, Amazon’s suite for image, audio, and video generation.

    Early testing shows promising results. One brand, Bird Buddy, saw a 338% jump in ad click-through rates using the AI-built campaign.

    But the push goes beyond advertising.

    Seller Assistant can now manage inventory, flag products at risk of breaching safety rules, suggest pricing changes, and even propose new product categories based on shopper behaviour.

    Amazon says the system is designed to free up small and medium businesses to focus on product development while AI handles day-to-day operations.

    The upgrades arrive as Amazon’s ad business continues to soar, generating an estimated A$90–100 billion annually with 23% year-on-year growth in its most recent quarter.

    Locally, Amazon has not yet confirmed when the new AI-powered features will launch for Australian sellers, but the company has promised a global rollout in the coming months.

    The post Amazon Unveils AI Tool That Builds Entire Ad Campaigns For Merchants appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/amazon-unveils-ai-tool-that-builds-entire-ad-campaigns-for-merchants/feed/ 0
    Mosman Restaurant Basil Nut, Still Ripping Off Customers With High Credit Card Charges https://smartoffice.com.au/mosman-restaurant-basil-nut-still-ripping-off-customers-high-credit-card-charges/ https://smartoffice.com.au/mosman-restaurant-basil-nut-still-ripping-off-customers-high-credit-card-charges/#respond Wed, 15 Nov 2017 08:05:44 +0000 http://smartoffice.com.au/?p=95775 Mosman is known for being an expensive suburb to live but it now appears that retailers are openly flaunting the law by charging up to 5% on credit card transactions despite a ban being introduced as to the amount that retailers can charge. Basil Nut a local Thai resturant is charging 5% on transactions and ... Read more

    The post Mosman Restaurant Basil Nut, Still Ripping Off Customers With High Credit Card Charges appeared first on Smart Office.

    ]]>
    Mosman is known for being an expensive suburb to live but it now appears that retailers are openly flaunting the law by charging up to 5% on credit card transactions despite a ban being introduced as to the amount that retailers can charge.

    Basil Nut a local Thai resturant is charging 5% on transactions and they are not advising customers of the additional charge, they appear to have ignored the recent 1% and 1.5% rule introduced in Australia.

    I discovered the rip off when I recently ordered a takeaway meal at their Mosman restaurant.

    After totalling the bill I queried why there was an additional 5% charge for using a credit card Vs paying cash.

    They claimed it was “because of bank fees”.

    When we explained that the law has recently changed and that business such as Basil Nut can only charge a maximum fee of 1.5% they claimed that because they were a small business they felt they could charge the additional 5% fee.

    When it was explained that From September 1, all Australian businesses will be banned from slugging customers with excessive surcharges for using EFTPOS and credit cards to pay for purchases management at the business who go out of their way to solicite cash sales said that this was “The way we do business”.

    It appeared that management were not interested in the new laws especially as they were netting an additional 5% on credit card transactions.

    When we asked whether the 5% additional fee applied to orders placed over the phone on a credit card management did not answer my question.

    Ban on excessive credit card surcharges extends to all Australian businesses September 1

    Customers should expect credit card surcharges to be no more than 1-3pc, depending on the type of card

    The Australian Competition and Consumer Commission (ACCC) said the ban, which has been in effect for large businesses since last September, will extend to all businesses that are either based in Australia or use an Australian bank.

    The ban means businesses will only be able to charge customers what it actually costs them to process payments for EFTPOS, MasterCard, Visa and American Express cards, including bank fees and terminal costs.

    “For example, if a business’s cost of acceptance for Visa credit is 1.5 per cent, consumers can only be charged a surcharge of 1.5 per cent on payments made using a Visa credit card,” ACCC deputy chairman Michael Schaper said.

    “Our message to business is that you are not allowed to add on any of your own internal costs when calculating what surcharge you will charge customers.

    “The only costs businesses can include are external costs charged to you by your financial provider.”

    The ACCC says businesses that want to set a single, flat surcharge across multiple payment methods must set the surcharge at the level of the lowest cost method, not an average.

    “Our advice for businesses wanting to set a single surcharge regardless of the type of card their customers use, is it must be the lowest of all the payment methods,” Dr Schaper added.

    “You can’t use an average of all payment methods or you will land yourself in trouble.”

    That means if a business’s cost of processing for Visa debit is 1 per cent, while its cost for Visa credit is 1.5 per cent and for American Express is 2.5 per cent, the single surcharge would need to be 1 per cent, because that is the lowest of all payment methods.

    Small businesses ‘can’t afford not to pass costs on’

    The ACCC said the changes will affect billions of transactions each year and are a big win for consumers.

    The post Mosman Restaurant Basil Nut, Still Ripping Off Customers With High Credit Card Charges appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/mosman-restaurant-basil-nut-still-ripping-off-customers-high-credit-card-charges/feed/ 0
    Singles Day Rakes In $25bn Haul https://smartoffice.com.au/singles-day-rakes-in-25bn-haul/ https://smartoffice.com.au/singles-day-rakes-in-25bn-haul/#respond Sun, 12 Nov 2017 23:04:23 +0000 http://smartoffice.com.au/?p=95753 SHANGHAI – Chinese e-commerce giant Alibaba says its Singles’ Day sales extravaganza hit US$25.4 billion over the weekend, smashing its own record from last year and cementing it as the world’s biggest shopping event. Once a celebration for China’s lonely hearts, Singles’ Day 11/11 has become an annual 24-hour buying frenzy that exceeds the combined sales ... Read more

    The post Singles Day Rakes In $25bn Haul appeared first on Smart Office.

    ]]>
    SHANGHAI – Chinese e-commerce giant Alibaba says its Singles’ Day sales extravaganza hit US$25.4 billion over the weekend, smashing its own record from last year and cementing it as the world’s biggest shopping event.

    Once a celebration for China’s lonely hearts, Singles’ Day 11/11 has become an annual 24-hour buying frenzy that exceeds the combined sales for Black Friday and Cyber Monday in the USA, offering a barometer for China’s consumers.

    At midnight pre-orders drove Alibaba’s platforms to ring up $10 billion in just over an hour.

    As tills shut down at midnight on Saturday, Alibaba’s live sales-ticker registered 168.3 billion yuan (A$33 billion), up 39 percent from last year.

    The post Singles Day Rakes In $25bn Haul appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/singles-day-rakes-in-25bn-haul/feed/ 0
    JB Hi Fi Charman Talks About Their Youth Brand, Parallel Importing & Selling Appliances Online https://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online-2/ https://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online-2/#respond Thu, 06 Jul 2017 05:31:03 +0000 http://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online-2/ EXCLUSIVE: As Harvey Norman moves online, JB Hi-Fi has moved to restructure their operations for what their Chairman describes will be a "record year". He also said that the future is "low cost" retailing and that parallel importing is a "temporary issue".

    The post JB Hi Fi Charman Talks About Their Youth Brand, Parallel Importing & Selling Appliances Online appeared first on Smart Office.

    ]]>
    EXCLUSIVE: As Harvey Norman moves online, JB Hi-Fi has moved to restructure their operations for what their Chairman describes will be a “record year”. He also said that the future is “low cost” retailing and that parallel importing is a “temporary issue”.

    In an exclusive interview with ChannelNews JB Hi-Fi Chairman Patrick Elliott said that JB Hi Fi was Australia’s most recognised “youth brand” and that the group is still on track to open between 13 and 15 new stores, some of which will be converted Clive Anthony stores.

    He said that the youth of today, who are shopping at JB HI Fi will be their customers in the future, if they “do the right thing by them. The opportunity we have to leverage that youth brand into online is significant. This is an audience that is used to online trading and recognise brand values”.

    Speaking about retailers who have an older target audience Elliott said: “These retailers will reach a point where they are going to have to renew their customer set”.

    He claimed that Internet trading is set to be an issue for many retailers in the future and that parallel importing is a “temporary issue” that will go away as the market re-adjusts to lower prices.

    “Local manufacturers realise that they are losing control of global supply. As a result they are already starting to lower their costs in Australia in an effort to stay competitive” Elliott said.

    “It will get to the stage where bringing in small shipments of goods from overseas will not be cost effective and due to the lowering of prices in Australia driven by online the market will rebalance itself” he said.

    Last week, JB Hi Fi alerted the market to the restructure of their struggling Clive Anthony brand, with Elliott admitting, during a recent interview on the ABC program Inside Business, that some stores will be closed. He described Clive Anthony as an experiment “that hasn’t succeeded in its current format”.

     

    One of the options now being considered by JB Hi Fi is to launch an online appliance store, a move which Elliott admits will appeal to a lot of consumers due to the high replacement factor that appliances attract.

    “Going online makes a lot of sense. We want to be a low cost retailer. We are competing against retailers who have expensive property structures such as department stores. If department stores want to carry on selling appliance they are going to have to deliver exceptional in store service to keep attracting customers. The future is low cost which is where JB Hi Fi is operating”.

    Elliott said that net profit at JB Hi Fi for the 12 months to June was now likely to be $108.5 million to $113.5 million, compared with the previous forecast of between $134 million and $139 million.

    “I think there are plenty of good days still to come, we have an excellent management team at JB Hi Fi, and they love their jobs and are well rewarded for it. I think our model is right,” he said.

    Elliott also said that closing down its underperforming discount electrical retailing brand, Clive Anthony’s was “one of the options” that the group was considering.

    “The expectation is that we’ll be able to convert quite a few of those stores to JB Hi-Fi stores,” Mr Elliott said during his ABC interview.

    “We’re looking at perhaps a change to the format for those stores, but remaining in appliance retailing, although it may be on a somewhat smaller scale, and some of those locations which aren’t profitable we will look to close.”

    “The internet model is particularly well suited to do that, whereas the bricks and mortar model is less so, so this discussion is very much more around where we are with currency, perhaps, than any significant change in technology or the uptake in technology.”

     

     

    Elliott said JB Hi-Fi’s online business was growing, but stressed that its “bricks and mortar” model was “very low-cost”.

    “Our cost of doing business, which is everything below the gross margin line, is about 14, 14.5 per cent of sales,” Elliott said. “There wouldn’t be too many online retailers who have that low cost base, and so we’re comfortable that with our scale, our buying power and our low cost to business that we can compete quite effectively with an online retailer, and to that extent clearly pushing our own online model.”

    Suppliers of software and possibly cameras would also adjust their domestic prices to match international prices thanks to the “lag effect” of the rising dollar, Elliott said.

    The post JB Hi Fi Charman Talks About Their Youth Brand, Parallel Importing & Selling Appliances Online appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online-2/feed/ 0
    GNC LiveWell Sale Campaign Misleading Shoppers https://smartoffice.com.au/gnc-livewell-sale-campaign-misleading-shoppers/ https://smartoffice.com.au/gnc-livewell-sale-campaign-misleading-shoppers/#respond Tue, 04 Jul 2017 05:00:00 +0000 http://smartoffice.com.au/gnc-livewell-sale-campaign-misleading-shoppers/ The NSW Department of Fair Trading is set to be asked to investigate the activities of GNC LiveWell which operates retail health stores in Australia following complaints that the company is engaging in misleading marketing.

    The post GNC LiveWell Sale Campaign Misleading Shoppers appeared first on Smart Office.

    ]]>
    The NSW Department of Fair Trading is set to be asked to investigate the activities of GNC LiveWell which operates retail health stores in Australia following complaints that the company is engaging in misleading marketing.

    Currently the Australian operation which is part of a Singapore based GNC LiveWell franchise is running an up to 40% discount sale based on 25% off for two items 30% for 3 items up to 40% off for 4 items.  However when customers go to collect the discounts they are told that they are not available.

    Renowned for their expensive vitamins and health care products, GNC LiveWell is the world’s largest health retailer specialising in herb, vitamin, weight management and sports nutrition. GNC has over 6,000 stores in 40 countries worldwide.

    Investigations by SmartOffice of GNC LiveWell stores such as the one at North Sydney’s Greenwood Plaza found that the stores are plastered with point of sale material advertising the offer. Shelves of vitamins have pointers that say “20% 30% 40% off these items.

    Click to enlarge


    The only problem is that the discounts are not available on many of the advertised items despite advertising indicating that discounts are available.

     

    When SmartOffice visited the North Sydney store we collected two items from shelves marked with the discount offer. At the checkout till the items were rung up with GNC LiveWell staff asking for the full retail price of the items which were not cheap at $89.50.

    When questioned why the 25% discount was not being applied the staff said, “The discounts do not apply to these items” When we point to the shelf where the goods had come from along with the poster saying 20% 30% 40% off these items they said, “It is all marketing, we are not giving discounts to items on those shelves. The discounts apply to other products.”

    When we questioned as to why pointers were on the shelves where the items had come from indicating that discounts were available a manager stepped in saying, “It is all marketing, everyone does it”.

    When we pointed out to her one of the posters that promoted the offer of a 25% discount off two items she said, “Conditions apply”. When it was pointed out that the word conditions apply was not showing she lifted up the plastic display item to show the words in 8point type hidden from view.

    Other posters in the store also had the words conditions apply yet when we asked to see those conditions the manager was unable to produce the written conditions.

    When it was put to her that this was blatantly misleading she said, “It’s all advertising and marketing, everyone does it we are not alone. If you want to take the issue up with management call our head office.”

    The post GNC LiveWell Sale Campaign Misleading Shoppers appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/gnc-livewell-sale-campaign-misleading-shoppers/feed/ 0
    JB Hi Fi Charman Talks About Their Youth Brand, Parallel Importing & Selling Appliances Online https://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online/ https://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online/#respond Tue, 04 Jul 2017 03:18:59 +0000 http://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online/ EXCLUSIVE: As Harvey Norman moves online, JB Hi-Fi has moved to restructure their operations for what their Chairman describes will be a "record year". He also said that the future is "low cost" retailing and that parallel importing is a "temporary issue".

    The post JB Hi Fi Charman Talks About Their Youth Brand, Parallel Importing & Selling Appliances Online appeared first on Smart Office.

    ]]>
    EXCLUSIVE: As Harvey Norman moves online, JB Hi-Fi has moved to restructure their operations for what their Chairman describes will be a “record year”. He also said that the future is “low cost” retailing and that parallel importing is a “temporary issue”.

    In an exclusive interview with ChannelNews JB Hi-Fi Chairman Patrick Elliott said that JB Hi Fi was Australia’s most recognised “youth brand” and that the group is still on track to open between 13 and 15 new stores, some of which will be converted Clive Anthony stores.

    He said that the youth of today, who are shopping at JB HI Fi will be their customers in the future, if they “do the right thing by them. The opportunity we have to leverage that youth brand into online is significant. This is an audience that is used to online trading and recognise brand values”.

    Speaking about retailers who have an older target audience Elliott said: “These retailers will reach a point where they are going to have to renew their customer set”.

    He claimed that Internet trading is set to be an issue for many retailers in the future and that parallel importing is a “temporary issue” that will go away as the market re-adjusts to lower prices.

    “Local manufacturers realise that they are losing control of global supply. As a result they are already starting to lower their costs in Australia in an effort to stay competitive” Elliott said.

    “It will get to the stage where bringing in small shipments of goods from overseas will not be cost effective and due to the lowering of prices in Australia driven by online the market will rebalance itself” he said.

    Last week, JB Hi Fi alerted the market to the restructure of their struggling Clive Anthony brand, with Elliott admitting, during a recent interview on the ABC program Inside Business, that some stores will be closed. He described Clive Anthony as an experiment “that hasn’t succeeded in its current format”.

     

    One of the options now being considered by JB Hi Fi is to launch an online appliance store, a move which Elliott admits will appeal to a lot of consumers due to the high replacement factor that appliances attract.

    “Going online makes a lot of sense. We want to be a low cost retailer. We are competing against retailers who have expensive property structures such as department stores. If department stores want to carry on selling appliance they are going to have to deliver exceptional in store service to keep attracting customers. The future is low cost which is where JB Hi Fi is operating”.

    Elliott said that net profit at JB Hi Fi for the 12 months to June was now likely to be $108.5 million to $113.5 million, compared with the previous forecast of between $134 million and $139 million.

    “I think there are plenty of good days still to come, we have an excellent management team at JB Hi Fi, and they love their jobs and are well rewarded for it. I think our model is right,” he said.

    Elliott also said that closing down its underperforming discount electrical retailing brand, Clive Anthony’s was “one of the options” that the group was considering.

    “The expectation is that we’ll be able to convert quite a few of those stores to JB Hi-Fi stores,” Mr Elliott said during his ABC interview.

    “We’re looking at perhaps a change to the format for those stores, but remaining in appliance retailing, although it may be on a somewhat smaller scale, and some of those locations which aren’t profitable we will look to close.”

    “The internet model is particularly well suited to do that, whereas the bricks and mortar model is less so, so this discussion is very much more around where we are with currency, perhaps, than any significant change in technology or the uptake in technology.”

     

     

    Elliott said JB Hi-Fi’s online business was growing, but stressed that its “bricks and mortar” model was “very low-cost”.

    “Our cost of doing business, which is everything below the gross margin line, is about 14, 14.5 per cent of sales,” Elliott said. “There wouldn’t be too many online retailers who have that low cost base, and so we’re comfortable that with our scale, our buying power and our low cost to business that we can compete quite effectively with an online retailer, and to that extent clearly pushing our own online model.”

    Suppliers of software and possibly cameras would also adjust their domestic prices to match international prices thanks to the “lag effect” of the rising dollar, Elliott said.

    The post JB Hi Fi Charman Talks About Their Youth Brand, Parallel Importing & Selling Appliances Online appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/jb-hi-fi-charman-talks-about-their-youth-brand-parallel-importing-selling-appliances-online/feed/ 0
    ACS Favours Corp Express https://smartoffice.com.au/acs-favours-corp-express/ https://smartoffice.com.au/acs-favours-corp-express/#respond Thu, 30 Dec 1999 13:36:00 +0000 http://smartoffice.com.au/acs-favours-corp-express/ Corporate Express has secured a deal as 'preferred supplier' to the Australian Computer Society.

    The post ACS Favours Corp Express appeared first on Smart Office.

    ]]>
    Corporate Express has secured a deal as ‘preferred supplier’ to the Australian Computer Society.

    The deal will see the 14,000 members of the ACS get a 10 per cent discount on purchases made while Corporate Express will also donate a percentage of revenue directly to the ACS for industry development.

    Jo Cass, Software and Licensing Manager NSW/ACT for Corporate Express, said “We are very confident that this partnership will prove successful for both parties and will drive great benefits through value and efficiencies through the ACS membership.

    “Our mission is to provide a single source supply solution to make it easier and more cost effective for our customers to do business. The offer is beneficial to members because by consolidating suppliers they are increasing efficiencies around the procurement and the administration process,” she said.

    “We are also providing bi-monthly offers on a variety of products, unique to the ACS membership and will be a significant reduction on current market pricing. Also, by spending with us, members will be putting money back into the IT industry, as a percentage of all ACS membership spend will be donated to the ACS foundation to drive IT apprenticeships,” she said.

    Simon Kwan, Marketing Manager for the Australian Computer Society, said they chose Corporate Express due to the demand of their members’ requirements for IT products and services. “We came to the conclusion that the best partner was Corporate Express because it is a reputable organisation and they offer nationwide service.

    www.ce.com.au

     

    The post ACS Favours Corp Express appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/acs-favours-corp-express/feed/ 0
    Get To Indy in October https://smartoffice.com.au/get-to-indy-in-october/ https://smartoffice.com.au/get-to-indy-in-october/#respond Thu, 30 Dec 1999 13:32:00 +0000 http://smartoffice.com.au/get-to-indy-in-october/ Acer is offering resellers a chance to earn a VIP Lexmark Indy 300 Experience by selling Intel Centrino notebooks and Hyper Threading desktops.

    The post Get To Indy in October appeared first on Smart Office.

    ]]>
    Acer is offering resellers a chance to earn a VIP Lexmark Indy 300 Experience by selling Intel Centrino notebooks and Hyper Threading desktops.

    Step one, sign-up here. Step two, sell a heap of gear. Step three, log your sales. Step four, win a trip for 1 adult to the Lexmark Indy 300 on the Gold Coast.
    The prize is valued at up to $4,000.00 each depending on point of departure, including return economy airfares from winner’s nearest capital city, 3 nights at the Marriott Hotel, and a VIP ticket to the Acer Chicane Luxury Suite.
    Sales made between 15 July and 30 September this year qualify, but it’s not just the volume sales achievers that will be in the running to take the trip.
    To give smaller resellers a chance, Acer is dividing the goodies up categories.
    The three channel salespeople with the highest unit sales will earn a VIP prize pack. The next 50 channel salespeople with the highest unit sales will receive a minor prize. This consists of two General Admission tickets to the Lexmark Indy 300, valued at $300.
    The outright leader, the channel sales person with the overall highest unit sales also receives an Overachievers Pack which includes a 15 minute helicopter ride, a Pace Car ride around the track and an Indy Merchandise Pack valued at up to $2,000.
    If you haven’t got a chance at leading the unit sales race, Acer is offering the top three channel salespeople with the highest growth the same package. The outright sales growth winner will also earn an overachiever pack with the chopper, pace car merchandise pack.

    The post Get To Indy in October appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/get-to-indy-in-october/feed/ 0
    In Store Retail Displays Stir Up CE Channel https://smartoffice.com.au/in-store-retail-displays-stir-up-ce-channel/ https://smartoffice.com.au/in-store-retail-displays-stir-up-ce-channel/#respond Thu, 30 Dec 1999 13:28:00 +0000 http://smartoffice.com.au/in-store-retail-displays-stir-up-ce-channel/ Mass retailers are moving to change their consumer engagement processes with the introduction of new in-store displays which are paid for by suppliers a move that has upset some distributors.

    The post In Store Retail Displays Stir Up CE Channel appeared first on Smart Office.

    ]]>
    Mass retailers are moving to change their consumer engagement processes with the introduction of new in-store displays which are paid for by suppliers a move that has upset some distributors.

    While big brand suppliers like Samsung, LG, Toshiba and Telstra have welcomed the move some suppliers are not happy telling ChannelNews that they don’t have the margins to pay for” in store displays, catalogue advertising and in some cases floor space”.

    Scott Browning the Marketing Director at JB Hi Fi claims that consumer electronics marketing today is all about “consumer engagement” and that to be successful retailers have to change their engagement model.

    “Our in store staff have to be able to demonstrate a product as well as have knowledge about a product and an in store displays allows us to range products in a way that we can not only show the product but in many cases show how a product works”. 

    He cites headphones as a category where the introduction of new in store displays for brands like Beats, Skull Candy, Sennheiser and Bowers & Wilkins have led to “significant” sales increases. 

    The introduction of working headphones connected to music along with the positioning of a mirror right next to a headphone is now standard in most JB Hi Fi stores. 

    Samsung Australia claims that their in store displays which they refer to as “Paragons” have delivered significant increases in sales because products are linked with products that consumers trust.

    Philip Newton, the vice president of consumer electronics at Samsung Electronics Australia said “When we range a camera or a PC or an audio product on one of our displays that is next to a Samsung TV the consumer is more likely to trust the brand. Many Australians watch a Samsung TV of an evening and they trust the product and the brand so when they go into a store and see another Samsung product they are likely to buy it because it is associated with a brand that is trusted”. 

    Currently Samsung is planning a major new retail drive with new point-of-sale “Paragons” to be placed in leading retail chains complete with a “Samsung ambassador” on hand to answer customer questions, explain products and train in-store staff.
    The stands will be stocked with tablets, smartphones and computers, large-screen TVs and Samsung’s new home theatre range.

    The concept was first trialled in Harvey Norman Auburn before being rolled out into 70 stores across Australia in 2012. Now the original 70 stands will be retrofitted and Samsung has plans to have the new Paragons in 200 stores by the end of the year.

    It will start next week, with stands in some JB Hi-Fi, Harvey Norman, Bing Lee and Good Guys stores.

    The concept has some parallels with Apple’s in-store installations in retailers including Harvey Norman, Myer and David Jones, in some cases staffed by Apple employees. Toshiba at its recent new product launch also revealed plans for in-store consoles.

     


    The move which is costly has led to increased sales for both distributors and manufacturers. Geoff Mathews the CEO of Convoy the distributor of JBL, Monster, Bowers & Wilkins and Harman Kardon products said “We have invested significantly in working with retailers to deliver in store displays and we are reaping the benefits. Originally the displays were costly but we have worked on our displays and we have been able to lower the cost while improving the display to include interactive capabilities by working with Chinese manufacturers who are able to deliver a cost effective solution”. 

    Three distributors who are currently range products at Harvey Norman and JB Hi Fi told ChannelNews that the move by retailers to “demand” in store displays was “costly” and that the margins were not there to support “this type of marketing”.

    One audio distributor said “mass retailers want suppliers to pay for everything from the marketing of a product to now having in store displays, some are even asking us to pay for floor space. This is not sustainable for smaller distributors and it will lead to several products disappearing from the Australian market. It will also drive consumers to overseas web sites”.

    “Australian retailers don’t want to take any risks, stock is supplied as sale and return and they want us to now invest in displays which have to be changed on a regular basis.”

    Another distributor said “Some retailers are pushing all the risk and the marketing onto distributors who don’t have the marketing resources or marketing knowledge to compete. We are distributors who take the risk on a product selling after we have taken the risk of importing the product. Retailers need to invest more of their money in the marketing of a product; they are demanding bigger margins while asking us to take all the risk”.

    Scott Browning said “In store displays suite certain brands, we recognise that some distributors have to go back to their manufacturer to try and get marketing dollars to support an in store display. The big manufacturers are not treating their displays as a co-op funding cost, it is coming out of their above the line marketing budgets because they recognise the value of engaging with a consumer in store”.

    He added “Apple raised the bar both in their own stores and with their retail partners like JB Hi Fi, their products are engineered as part of the design process so that they can be left on for in store display purposes. Today people have an expectation when they go into a store and what we have to do is merchandise a product in a way that we get a per square metre return. Our rents are not coming down so it is important that we work to maximise a return for both JB Hi Fi and our partners, we are working to get a balance that is right for both parties”.

    The post In Store Retail Displays Stir Up CE Channel appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/in-store-retail-displays-stir-up-ce-channel/feed/ 0
    Who ARE Oz Retail Thiefs? https://smartoffice.com.au/who-are-oz-retail-thiefs/ https://smartoffice.com.au/who-are-oz-retail-thiefs/#respond Thu, 30 Dec 1999 13:25:00 +0000 http://smartoffice.com.au/who-are-oz-retail-thiefs/ Australia's most wanted: Batteries, petrol and razor blades are the most sought-after items among store thieves.

    The post Who ARE Oz Retail Thiefs? appeared first on Smart Office.

    ]]>
    Australia’s most wanted: Batteries, petrol and razor blades are the most sought-after items among store thieves.
    When the going gets tough, the thieves get going. So says ARA Exec Director Russell Zimmermann who says stealing from stores is the direct result of when things get tough for people economically.

    “There will be a rise in stealing in some areas..especially things that make them feel good,” Zimmermann said when speaking to Channel News.

    Indeed, stealing from stores has risen in the past number of years as it is a clear reflection of the uncertain economic environment.

    Just last week, JB Hi-Fi said $12 million of stock from its stores in the past year had been stolen, an increase of $4m on the previous year, which it blamed on less staff on the floor. Headphones and DVDs were said to be among the items popular with thieves at JB.

    “Its hard to walk out the door with a 40″ TV,” says Zimmermann, but smaller items that are easy to conceal are fair game, as are goods that are easy to remove packaging and electronic barcodes from.

    So, who are the thieves scanning the store aisles?

    “They could be anyone from your next door neighbour ..its a myth that its unemployed 18-years old..its across the board.”

    “There’s no specific demographic profile of thieves,” – for instance women are known to be fond of thieving clothes and other sought after items.

     

    3% of the total Aussie retail turnover of $240 billion is attributed to theft, according to Australian Retailers Association.

    In 2010, $5.8bn worth of goods was stolen which has risen to $6bn this year.

    The retail boss also said it was “interesting” that Apple recently announced a ‘self-help area’ in its stores, where consumers can virtually check out goods in-store, by downloading an app.

    Maybe Apple customers are honest. And really rich. 

    The post Who ARE Oz Retail Thiefs? appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/who-are-oz-retail-thiefs/feed/ 0