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; } } Isabella Alexiou, Author at Smart Office https://smartoffice.com.au/author/isabella-alexiou/ Tue, 09 Sep 2025 04:32:19 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 IFA 2025: Acer Launches TravelMate X14 AI Business Laptop to Empower SMBs and Hybrid Workforces https://smartoffice.com.au/ifa-2025-acer-launches-travelmate-x14-ai-business-laptop-to-empower-smbs-and-hybrid-workforces/ https://smartoffice.com.au/ifa-2025-acer-launches-travelmate-x14-ai-business-laptop-to-empower-smbs-and-hybrid-workforces/#respond Tue, 09 Sep 2025 04:32:19 +0000 https://smartoffice.com.au/?p=98778 Acer has unveiled its TravelMate X4 14 AI business laptop and two sustainable Vero laser projectors, targeting small and medium businesses (SMBs) and hybrid workforces with AI-enhanced productivity tools and eco-conscious design. The announcements at IFA 2025 reflect growing demand for intelligent business solutions that balance performance, mobility, and environmental responsibility. The TravelMate X4 14 ... Read more

    The post IFA 2025: Acer Launches TravelMate X14 AI Business Laptop to Empower SMBs and Hybrid Workforces appeared first on Smart Office.

    ]]>
    Acer has unveiled its TravelMate X4 14 AI business laptop and two sustainable Vero laser projectors, targeting small and medium businesses (SMBs) and hybrid workforces with AI-enhanced productivity tools and eco-conscious design.

    The announcements at IFA 2025 reflect growing demand for intelligent business solutions that balance performance, mobility, and environmental responsibility.

    The TravelMate X4 14 AI (TMX414-51) is designed specifically for SMBs and mobile professionals requiring robust performance in a portable package.

    Powered by up to Intel Core Ultra 7 Processor 258V delivering 115 TOPS of overall performance, it runs Windows 11 Pro with full Copilot+ PC experiences.

    AI features include Recall (preview), Click to Do, improved Windows search, Cocreator in Microsoft Paint, and Live Captions for real-time multilingual translations.

    These tools aim to enhance productivity and collaboration for distributed teams.

    Weighing just 1.27kg and measuring 15.9mm thin, the laptop doesn’t sacrifice durability for portability.

    It meets MIL-STD 810H standards, withstanding vibrations, humidity, and extreme temperatures, essential for professionals who travel frequently.

    “As AI is becoming increasingly important in modern business workflows, small and medium businesses need devices that integrate intelligent tools, robust security, and mobility,” said James Lin, General Manager, Notebooks, Acer Inc.

    The 14-inch 16:10 WUXGA display provides ample screen space for productivity tasks, with an optional OLED panel offering 500 nits peak brightness and vibrant colours.

    TÜV Rheinland Low Blue Light certification (hardware solution) supports better eye comfort during extended use.

    Acer PurifiedView 2.0 and PurifiedVoice 2.0 technology, paired with DTS:X Ultra Audio-enhanced speakers, ensure crystal-clear video calls, crucial for remote collaboration.

    Security features include Acer UserSensing 2.0, which uses a proximity sensor to dim and lock the screen when users step away.

    Connectivity encompasses Wi-Fi 7 and Bluetooth 5.4 for seamless streaming and lag-free video calls, plus two Thunderbolt 4 ports, USB 3.2 and USB 2.0 Type-A ports, HDMI 2.1, and Gigabit Ethernet for versatile peripheral support.

    Complementing the laptop, Acer introduced two mercury-free laser projectors designed for sustainable, long-term use.

    The Vero XL2521 and Vero SL2520n offer lifespans up to 30,000 hours and consume 40% less power than traditional lamp-based models.

    Both projectors deliver Full HD resolution and 4,000 ANSI lumens brightness with 93% Rec.709 colour gamut and 50,000:1 contrast ratio.

    They support 360-degree projection, portrait mode, and advanced 2D and 4-corner keystone correction for flexible installation.

    The SL2520n’s short-throw design produces a 100-inch image from just 1.1 metres, ideal for small meeting rooms.

    Both models feature IP6X dust-proof rating and 24/7 operation capability for demanding use cases, plus 15W built-in speakers with dedicated sports modes.

    Reinforcing sustainability commitments, both projectors incorporate 50% post-consumer recycled plastic in their chassis and use 100% recyclable packaging.

    The TravelMate X4 14 AI will be available in Australia (timing TBA) starting at $2,499, competing with business laptops from Lenovo ThinkPad and HP EliteBook series while offering superior AI integration.

    The Vero XL2521 launches in Australia and New Zealand Q4 2025 at $1,499 (NZD $1,899), while the Vero SL2520n arrives in Australia Q4 2025 at $1,999.

    These prices position them competitively against business projectors from Epson and BenQ.

    Microsoft’s Mark Linton, Vice President of Windows + Devices, noted: “With powerful performance, enhanced security, and unique AI experiences, it’s an ideal choice for today’s professionals looking to upgrade to a PC that prioritises portability and performance.”

    The launches demonstrate Acer’s commitment to the business sector, offering AI-enhanced productivity tools and sustainable solutions that address the evolving needs of modern workplaces.

    The post IFA 2025: Acer Launches TravelMate X14 AI Business Laptop to Empower SMBs and Hybrid Workforces appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/ifa-2025-acer-launches-travelmate-x14-ai-business-laptop-to-empower-smbs-and-hybrid-workforces/feed/ 0
    IFA 2025: Acer Unleashes Predator Orion 7000 Desktop for Elite Gaming Performance https://smartoffice.com.au/ifa-2025-acer-unleashes-predator-orion-7000-desktop-for-elite-gaming-performance/ https://smartoffice.com.au/ifa-2025-acer-unleashes-predator-orion-7000-desktop-for-elite-gaming-performance/#respond Tue, 09 Sep 2025 04:24:53 +0000 https://smartoffice.com.au/?p=98774 Acer has announced the Predator Orion 7000 desktop, a high-performance gaming system designed for enthusiasts, content creators, and professionals demanding uncompromised power and cutting-edge features. The desktop represents Acer’s flagship gaming offering for the Australian market, combining latest-generation processors, graphics, and an innovative cooling solution in a striking chassis design. The Predator Orion 7000 (PO7-667) ... Read more

    The post IFA 2025: Acer Unleashes Predator Orion 7000 Desktop for Elite Gaming Performance appeared first on Smart Office.

    ]]>
    Acer has announced the Predator Orion 7000 desktop, a high-performance gaming system designed for enthusiasts, content creators, and professionals demanding uncompromised power and cutting-edge features.

    The desktop represents Acer’s flagship gaming offering for the Australian market, combining latest-generation processors, graphics, and an innovative cooling solution in a striking chassis design.

    The Predator Orion 7000 (PO7-667) features up to Intel Core Ultra 9 processor 285K with an integrated NPU for AI workloads, ensuring lightning-fast processing for gaming, streaming, and multitasking.

    The processor’s AI capabilities extend beyond gaming, supporting content creation and productivity applications.

    Paired with up to NVIDIA GeForce RTX 5090 graphics, the system delivers ray-traced visuals and advanced AI capabilities including DLSS 4 with Multi Frame Generation.

    It also provides access to NVIDIA NIM Microservices, state-of-the-art AI models that allow enthusiasts and developers to build AI assistants, agents, and workflows with peak performance on NIM-ready systems.

    Memory and storage specifications match the high-end components, with support for up to 128GB DDR5 7200 MT/s XMP RGB RAM for seamless multitasking and memory-intensive applications.

    Storage options include up to 6TB SSD for blazing-fast load times, plus up to 4TB hard drive capacity with support for two 3.5-inch drives.

    To maintain peak performance during demanding sessions, the Predator Orion 7000 employs the Predator CycloneX 360 cooling system with CPU liquid cooler.

    The advanced thermal solution features a unique fan layout and open channel structure that boosts cooling efficiency by 15% and lowers motherboard temperatures by 9°C.

    This engineering ensures optimal airflow and heat dissipation, allowing components to operate at full potential without thermal throttling.

    The result is consistently stable gameplay even during extended gaming marathons or intensive rendering tasks.

    The system is housed in an EMI-compliant tempered glass chassis with customisable ARGB lighting, allowing users to personalise their setup’s appearance.

    The 45L chassis incorporates 65% post-consumer recycled plastic in its total plastic content, demonstrating environmental consideration without compromising on style.

    Connectivity options include Killer Ethernet E3100G and Wi-Fi 7 for low-latency online gaming, alongside Thunderbolt 4 for versatile high-speed connectivity with peripherals and external devices.

    The comprehensive I/O ensures compatibility with current and future gaming accessories.

    The desktop also features Acer Intelligence Space, a smart hub of AI applications designed to boost creativity and productivity beyond gaming applications.

    The Predator Orion 7000 will be available in Australia in late Q1 2026 from Harvey Norman, starting at $8,199.

    This pricing positions it against premium gaming systems from competitors like ASUS ROG Strix and MSI MEG series, though the inclusion of RTX 5090 graphics and advanced cooling may justify the premium.

    For Australian gamers and content creators seeking a no-compromise system with the latest technology, the Predator Orion 7000 represents Acer’s most ambitious gaming desktop to date, combining raw performance with thoughtful design and sustainability considerations.

    The post IFA 2025: Acer Unleashes Predator Orion 7000 Desktop for Elite Gaming Performance appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/ifa-2025-acer-unleashes-predator-orion-7000-desktop-for-elite-gaming-performance/feed/ 0
    IFA 2025: Acer Refreshes Nitro Lineup with Powerful New PC and Displays for Casual Gamers https://smartoffice.com.au/ifa-2025acer-floods-with-ai-workstations-720hz-gaming-monitors-and-sub-1kg-laptops/ https://smartoffice.com.au/ifa-2025acer-floods-with-ai-workstations-720hz-gaming-monitors-and-sub-1kg-laptops/#respond Mon, 08 Sep 2025 02:39:44 +0000 https://smartoffice.com.au/?p=98765 Acer has unveiled a refreshed Nitro gaming lineup featuring the portable V 16S laptop and two high-resolution gaming monitors, targeting casual and new gamers with powerful hardware at competitive price points. The new products combine modern gaming technology with reasonable pricing, offering alternatives to premium gaming systems while maintaining solid performance and visual quality. The ... Read more

    The post IFA 2025: Acer Refreshes Nitro Lineup with Powerful New PC and Displays for Casual Gamers appeared first on Smart Office.

    ]]>
    Acer has unveiled a refreshed Nitro gaming lineup featuring the portable V 16S laptop and two high-resolution gaming monitors, targeting casual and new gamers with powerful hardware at competitive price points.

    The new products combine modern gaming technology with reasonable pricing, offering alternatives to premium gaming systems while maintaining solid performance and visual quality.

    The Nitro V 16S (ANV16S-71) leads the refresh with a slim metal chassis measuring less than 19.9mm thick, making it highly portable for gaming on the go.

    Powered by up to Intel Core 7 processor 240H and NVIDIA GeForce RTX 5060 Laptop GPU, it supports DLSS 4 and neural rendering for responsive gameplay and content creation.

    The laptop’s WUXGA (1920×1200) display delivers 100% sRGB colour coverage and 180Hz refresh rate, ensuring accurate colours and ultra-smooth frames.

    A striking 4-zone RGB keyboard adds visual appeal to the modern aesthetic.

    NitroSense software enables real-time performance monitoring and fine-tuning of fan speeds for maximum cooling efficiency.

    The Acer Experience Zone provides access to AI-powered apps that boost productivity and creativity.

    Connectivity features include Intel Killer DoubleShot Pro for lag-free online gaming and Thunderbolt 4 for fast data transfer and seamless streaming.

    The combination of features positions the V 16S as an entry point for new PC gamers or those upgrading from older systems.

    Complementing the laptop, Acer introduced two new Nitro monitors offering immersive visuals across different formats and price points.

    The Nitro XV270X targets gamers and streamers demanding ultra-high resolution, delivering 5K (5120×2880) clarity with a 2,000:1 contrast ratio.

    The display features 1ms VRB response time and 95% DCI-P3 colour gamut for lifelike visuals, complemented by built-in 2W speakers.

    For those seeking an expansive gaming experience, the Nitro XZ403CKR presents a massive 39.7-inch 1000R curved panel with 5K WUHD (5120×2160) resolution.

    Its Dynamic Frequency Resolution (DFR) technology supports WFHD resolution at 288Hz, while 1ms VRB and 0.5ms GTG response times ensure fluid gameplay.

    AMD FreeSync Premium support eliminates screen tearing, while 5W speakers and extensive port options complete the premium gaming experience.

    The curved design enhances immersion, particularly in racing and flight simulation games.

    The Acer Nitro V 16S will be available in Australia from Harvey Norman in September 2025, starting at $2,599.

    This pricing positions it competitively against similar mid-range gaming laptops from ASUS TUF Gaming and MSI GF series.

    The Nitro XV270X will launch in Q1 2026 at $1,499, while the larger XZ403CKR arrives the same quarter at $1,899.

    Both monitors compete with high-resolution displays from Samsung Odyssey and LG UltraGear lines.

    The refreshed Nitro lineup demonstrates Acer’s commitment to the mainstream gaming market, offering modern features like DLSS 4, high refresh rates, and 5K resolution at prices accessible to casual gamers and content creators who don’t require flagship specifications.

    The post IFA 2025: Acer Refreshes Nitro Lineup with Powerful New PC and Displays for Casual Gamers appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/ifa-2025acer-floods-with-ai-workstations-720hz-gaming-monitors-and-sub-1kg-laptops/feed/ 0
    Marshall Brings Rock Heritage to Living Rooms with New Heston 60 Soundbar and Wireless Sub https://smartoffice.com.au/marshall-brings-rock-heritage-to-living-rooms-with-new-heston-60-soundbar-and-wireless-sub/ https://smartoffice.com.au/marshall-brings-rock-heritage-to-living-rooms-with-new-heston-60-soundbar-and-wireless-sub/#respond Thu, 04 Sep 2025 04:38:25 +0000 https://smartoffice.com.au/?p=98762 Marshall has unveiled the Heston 60 soundbar and Sub 200 wireless subwoofer, expanding its push into the home cinema market following the launch of the larger Heston 120 earlier this year. The British audio company, known for its iconic guitar amplifiers and recently prolific in wireless speakers, is targeting consumers with smaller living spaces who ... Read more

    The post Marshall Brings Rock Heritage to Living Rooms with New Heston 60 Soundbar and Wireless Sub appeared first on Smart Office.

    ]]>
    Marshall has unveiled the Heston 60 soundbar and Sub 200 wireless subwoofer, expanding its push into the home cinema market following the launch of the larger Heston 120 earlier this year.

    The British audio company, known for its iconic guitar amplifiers and recently prolific in wireless speakers, is targeting consumers with smaller living spaces who still want premium TV audio without dominating their room.

    The Heston 60 maintains Marshall’s signature aesthetic with its woven salt-and-pepper fret appearance and PU leather finish.

    Available in black or cream options, the soundbar features the brand’s distinctive control dials for adjusting volume, treble, and bass, a tactile approach that sets it apart from competitors relying solely on remote controls.

    Wall-mounting capabilities allow users to save additional space, making it ideal for apartments and smaller homes where floor space is at a premium.

    The soundbar offers extensive connectivity options for both TV and music streaming:

    • Wi-Fi integration
    • AirPlay 2 for Apple devices
    • Google Cast compatibility
    • Spotify Connect and Tidal Connect
    • Bluetooth with Auracast support for daisy-chaining multiple speakers

    This versatility positions the Heston 60 as more than just a TV soundbar, potentially serving as a primary music system for compact living spaces.

    Marshall’s new Sub 200 wireless subwoofer can pair with either the Heston 60 or the larger Heston 120, providing enhanced bass response for films, music, and TV content.

    Its wireless design allows flexible placement within the room for optimal acoustic performance.

    Both products can be calibrated through Marshall’s revamped app to optimise performance based on room characteristics and placement.

    The Heston 60 will retail for approximately $1,050 (based on USD $699 pricing), placing it in direct competition with established players like:

    • Sonos Beam Gen 2 ($799)
    • Sennheiser Ambeo Soundbar Mini ($1,199)
    • Bose Smart Soundbar ($899)

    The Sub 200 is priced at approximately $900 (USD $599), comparable to the Sonos Sub Mini’s market position.

    Marshall’s expansion into home cinema represents a strategic diversification from its traditional guitar amplifier heritage and recent wireless speaker launches.

    The company appears to be leveraging its brand recognition and design aesthetic to differentiate itself in the competitive soundbar market.

    With 2025 seeing multiple product launches from Marshall across various audio categories, the Heston 60 and Sub 200 demonstrate the brand’s commitment to becoming a comprehensive lifestyle audio provider rather than remaining confined to its musical instrument roots.

    Both the Heston 60 and Sub 200 will be available from Marshall and select retailers from September 30.

    Australian availability and local pricing are yet to be confirmed, though the products are expected to reach local shores shortly after the international launch, given Marshall’s established distribution network.

    The post Marshall Brings Rock Heritage to Living Rooms with New Heston 60 Soundbar and Wireless Sub appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/marshall-brings-rock-heritage-to-living-rooms-with-new-heston-60-soundbar-and-wireless-sub/feed/ 0
    BeefEater’s BIGG BUGG Goes Native with Eucalyptus Green Finish https://smartoffice.com.au/beefeaters-bigg-bugg-goes-native-with-eucalyptus-green-finish/ https://smartoffice.com.au/beefeaters-bigg-bugg-goes-native-with-eucalyptus-green-finish/#respond Thu, 04 Sep 2025 04:36:34 +0000 https://smartoffice.com.au/?p=98759 BeefEater has unveiled a new Eucalyptus Green colour way for its BIGG BUGG portable barbecue, offering Australian outdoor cooking enthusiasts a fresh aesthetic option ahead of the spring entertaining season. Priced at $999, the Eucalyptus Green BIGG BUGG (model BB722GB) maintains all the features of the existing range while introducing a contemporary colour that reflects ... Read more

    The post BeefEater’s BIGG BUGG Goes Native with Eucalyptus Green Finish appeared first on Smart Office.

    ]]>
    BeefEater has unveiled a new Eucalyptus Green colour way for its BIGG BUGG portable barbecue, offering Australian outdoor cooking enthusiasts a fresh aesthetic option ahead of the spring entertaining season.

    Priced at $999, the Eucalyptus Green BIGG BUGG (model BB722GB) maintains all the features of the existing range while introducing a contemporary colour that reflects Australian native flora.

    The new variant became available from September 1, 2025, positioning it perfectly for spring and summer barbecue season.

    The two-burner portable barbecue delivers 16MJ/hr of power through each independently controlled stainless steel burner, equipped with BeefEater’s Quartz Start Ignition system designed for reliable first-time starts.

    The dual temperature control system prevents flare-ups while maintaining even heat distribution across the cooking surface.

    The extra-large rust-resistant enamelled cast iron cooking surface offers a versatile plate-grill configuration, allowing users to barbecue with the hood up or down.

    An integrated thermometer and spring-assisted hood that lowers gently enhances the cooking experience and safety.

    Despite its substantial cooking capacity, the BIGG BUGG remains highly portable thanks to its smart wheel balance mechanism and robust trolley design.

    This mobility allows users to easily relocate the barbecue from balconies and patios to backyards and outdoor entertaining areas.

    Storage solutions include lockable side trays that provide additional preparation space when needed and fold away for compact storage.

    A large tray accommodates barbecue condiments and cooking accessories, keeping everything within easy reach during cooking sessions.

    BeefEater offers various accessories to enhance the BIGG BUGG experience, including protective covers, specialised tools, and a pizza stone set.

    A custom-designed cast iron hot plate accessory (sold separately) fits the BIGG BUGG perfectly, featuring ventilation points for even heat distribution, ideal for cooking burgers, eggs, pancakes, and fried onions in large quantities.

    At $999, the Eucalyptus Green BIGG BUGG competes in the premium portable barbecue segment, where similar two-burner models from competitors like Weber and Ziegler & Brown typically range from $800 to $1,200.

    The distinctive colour option differentiates it from the predominantly black and stainless steel offerings common in this category.

    The timing of the launch capitalises on Australian consumers preparing for the warmer months, when barbecue sales traditionally spike.

    The Eucalyptus Green colour way adds a design-conscious option for customers seeking outdoor cooking equipment that complements modern outdoor living spaces.

    The Eucalyptus Green BIGG BUGG is now available through BeefEater’s authorised dealer network across Australia, including major retailers like Barbeques Galore, Bunnings Warehouse, and speciality outdoor cooking stores.

    The standard BIGG BUGG warranty and after-sales support apply to the new colour variant.

    The post BeefEater’s BIGG BUGG Goes Native with Eucalyptus Green Finish appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/beefeaters-bigg-bugg-goes-native-with-eucalyptus-green-finish/feed/ 0
    Bluesound Launches Two Dolby Atmos Soundbars to Challenge Sonos Dominance https://smartoffice.com.au/bluesound-launches-two-dolby-atmos-soundbars-to-challenge-sonos-dominance/ https://smartoffice.com.au/bluesound-launches-two-dolby-atmos-soundbars-to-challenge-sonos-dominance/#respond Tue, 02 Sep 2025 06:15:11 +0000 https://smartoffice.com.au/?p=98751 Canadian audio brand Bluesound has introduced two new Dolby Atmos soundbars designed to compete with premium offerings from Sonos, combining what the company calls “audiophile-grade performance and everyday usability.” The Pulse Cinema represents the premium option at $2,315 AUD, featuring a 3.2.2 Dolby Atmos configuration with 16 speaker drivers, including a dedicated centre channel, dual ... Read more

    The post Bluesound Launches Two Dolby Atmos Soundbars to Challenge Sonos Dominance appeared first on Smart Office.

    ]]>
    Canadian audio brand Bluesound has introduced two new Dolby Atmos soundbars designed to compete with premium offerings from Sonos, combining what the company calls “audiophile-grade performance and everyday usability.”

    The Pulse Cinema represents the premium option at $2,315 AUD, featuring a 3.2.2 Dolby Atmos configuration with 16 speaker drivers, including a dedicated centre channel, dual four-inch woofers, and up-firing speakers.

    The 119cm-wide soundbar delivers 500 watts of total power and targets televisions above 55 inches according to Bluesound specifications.

    The more compact Pulse Cinema Mini, priced at $1,543 AUD, measures 84cm in width and provides 280 watts of system power.

    While lacking dedicated up-firing Atmos speakers, it incorporates angled drivers and 2.1-channel Atmos virtualisation for smaller room setups.

    Both models support HDMI eARC, optical, and analog inputs with included wall mounts for flexible positioning options.

    The soundbars integrate Bluesound’s BluOS platform, enabling compatibility with over 20 streaming services, including Tidal, Spotify, and Qobuz, while supporting high-resolution audio up to 24-bit/192kHz.

    Multi-channel surround system connectivity is available through the BluOS ecosystem, allowing integration with other Bluesound speakers for expanded audio setups.

    Design options include black with grey fabric grille or white with tan accent details.

    The Pulse Cinema faces direct competition from the Sonos Arc Ultra, which has received positive reviews for three-dimensional sound reproduction and bass performance.

    The Arc Ultra operates in a similar premium price range and offers comparable Dolby Atmos capabilities.

    The Pulse Cinema Mini competes against the established Sonos Arc, originally launched at $1,399 and recognised with industry awards for its performance in the mid-premium soundbar category.

    The Arc’s proven track record and lower price point present significant competitive challenges for Bluesound’s entry.

    Both Bluesound models will be available for pre-order starting September 24, entering a crowded soundbar market where established players like Sonos have built strong reputations through consistent performance and ecosystem integration.

    The success of Bluesound’s soundbar venture will depend on whether the company can differentiate its offerings through superior audio quality or unique features, given the competitive pricing landscape and Sonos’s established market presence in premium wireless audio products.

    The post Bluesound Launches Two Dolby Atmos Soundbars to Challenge Sonos Dominance appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/bluesound-launches-two-dolby-atmos-soundbars-to-challenge-sonos-dominance/feed/ 0
    Samsung Fills Monitor Gap With New 37-Inch ViewFinity S8 https://smartoffice.com.au/samsung-fills-monitor-gap-with-new-37-inch-viewfinity-s8/ https://smartoffice.com.au/samsung-fills-monitor-gap-with-new-37-inch-viewfinity-s8/#respond Tue, 02 Sep 2025 06:11:48 +0000 https://smartoffice.com.au/?p=98748 Samsung has introduced its first 37-inch monitor, the ViewFinity S8, positioning it between the existing 32-inch and 43-inch models in the series to offer what the company describes as “outstanding readability, an optimal viewing distance and a comfortable field of view.” The 37-inch display maintains a 16:9 aspect ratio and UHD resolution while providing additional ... Read more

    The post Samsung Fills Monitor Gap With New 37-Inch ViewFinity S8 appeared first on Smart Office.

    ]]>
    Samsung has introduced its first 37-inch monitor, the ViewFinity S8, positioning it between the existing 32-inch and 43-inch models in the series to offer what the company describes as “outstanding readability, an optimal viewing distance and a comfortable field of view.”

    The 37-inch display maintains a 16:9 aspect ratio and UHD resolution while providing additional workspace compared to the 32-inch version.

    Samsung states that the extra five inches allow text to appear larger with the same display settings, making information “easier to grasp at a glance” and supporting collaborative work scenarios involving standing presentations or screen sharing.

    TÜV Rheinland has certified the monitor as an “Ergonomic Workspace Display,” recognising its design for reducing visual fatigue during text editing and documentation tasks while enhancing task immersion.

    The display includes Intelligent Eye Care features that minimise blue light emission and flicker to reduce eye strain, also carrying TÜV certification.

    The ViewFinity S8 includes a built-in keyboard, video, and mouse switch, allowing control of two devices simultaneously through a single set of peripherals.

    Picture-by-picture and picture-in-picture modes support multitasking scenarios, with the former enabling laptop and smartphone content to occupy separate halves of the display.

    Connectivity options include a USB-C port offering 90W device charging capability and a built-in LAN port for wired network access.

    The Easy Setup Stand requires approximately 10 seconds for installation without additional tools or screws, while providing height adjustment, tilt, and swivel functions for positioning optimisation.

    Samsung also offers a gaming-focused version called the Odyssey G7 in 37-inch format, featuring 1000R curvature, 4K UHD resolution, 165Hz refresh rate, and 1ms response time, targeting immersive gaming experiences on larger screens.

    The introduction of the 37-inch size fills a gap in Samsung’s monitor lineup between traditional desktop displays and larger screens that approach television territory.

    The sizing strategy appears to target professional users seeking expanded workspace without the desk space requirements of 43-inch displays.

    However, Samsung has not disclosed pricing or availability details for either the ViewFinity S8 or Odyssey G7 37-inch models, leaving market positioning unclear relative to existing monitor options in the company’s portfolio and competitive alternatives from other manufacturers.

    The launch reflects ongoing industry trends toward larger desktop displays as remote work and content creation demands drive preference for expanded screen real estate, though the practical benefits of the specific 37-inch size over established alternatives remain to be proven in actual workplace environments.

    The post Samsung Fills Monitor Gap With New 37-Inch ViewFinity S8 appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/samsung-fills-monitor-gap-with-new-37-inch-viewfinity-s8/feed/ 0
    IFA 2025: Haier Launches 600 Series Freestanding Dishwasher With DualPower Technology https://smartoffice.com.au/ifa-2025-haier-launches-600-series-freestanding-dishwasher-with-dualpower-technology/ https://smartoffice.com.au/ifa-2025-haier-launches-600-series-freestanding-dishwasher-with-dualpower-technology/#respond Tue, 02 Sep 2025 06:09:05 +0000 https://smartoffice.com.au/?p=98745 Haier has introduced its 600 Series freestanding dishwasher featuring DualPower Spray Arm technology and advanced drying capabilities, available in Satina and Black finishes for the Australian and New Zealand markets. The dishwasher incorporates DualPower Spray Arm technology that moves water in both directions to double water coverage in the lower basket compared to traditional spray ... Read more

    The post IFA 2025: Haier Launches 600 Series Freestanding Dishwasher With DualPower Technology appeared first on Smart Office.

    ]]>
    Haier has introduced its 600 Series freestanding dishwasher featuring DualPower Spray Arm technology and advanced drying capabilities, available in Satina and Black finishes for the Australian and New Zealand markets.

    The dishwasher incorporates DualPower Spray Arm technology that moves water in both directions to double water coverage in the lower basket compared to traditional spray arms.

    This system aims to provide more thorough cleaning of dishes through enhanced water distribution patterns.

    The unit operates at 42dBA for quiet kitchen operation and includes dual LED lights for improved basket visibility during loading and unloading.

    The design integrates with other Haier appliances while offering Wi-Fi connectivity through the SmartHQ app for remote control functionality.

    The CutleryPlus system uses five rotating nozzles designed to improve water coverage by up to 25% compared to standard spray arm configurations.

    GlassGrip technology secures glassware and cups during wash and dry cycles to prevent movement and potential damage.

    Aquastop technology provides leak detection and automatic water supply shutdown for additional protection.

    The dishwasher includes adjustable middle basket accommodation for dishes up to 320mm height, along with foldable tines, cup racks, and wine glass supports for loading flexibility.

    The appliance features automatic door opening at cycle completion, combined with fan circulation to provide energy-efficient drying.

    A Dry+ modifier option enhances drying performance specifically for plastic items that typically retain moisture.

    Steam functionality works alongside the spray systems for enhanced cleaning performance, while the spacious design accommodates various dish sizes and shapes.

    The unit includes a two-year warranty with 24/7 local service support for Australian and New Zealand customers.

    Haier positions the 600 Series as its most advanced dishwasher offering, targeting households seeking enhanced cleaning performance and convenience features.

    The combination of DualPower spray technology, automatic drying, and smart connectivity reflects current trends toward more sophisticated home appliances.

    The availability of two finish options provides aesthetic flexibility for different kitchen designs, while the emphasis on quiet operation addresses consumer preferences for less intrusive appliance performance during daily use.

    However, Haier has not disclosed pricing information for either the Satina (HDW15F4S1) or Black (HDW15F4B1) models, making it difficult to assess market positioning relative to competing dishwasher offerings from established appliance manufacturers.

    The post IFA 2025: Haier Launches 600 Series Freestanding Dishwasher With DualPower Technology appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/ifa-2025-haier-launches-600-series-freestanding-dishwasher-with-dualpower-technology/feed/ 0
    IFA 2025: Stair-Climbing Robot Vacuums? Dreame Hints at Game-Changing Tech https://smartoffice.com.au/ifa-2025-stair-climbing-robot-vacuums-dreame-hints-at-game-changing-tech/ https://smartoffice.com.au/ifa-2025-stair-climbing-robot-vacuums-dreame-hints-at-game-changing-tech/#respond Tue, 02 Sep 2025 06:04:42 +0000 https://smartoffice.com.au/?p=98742 Dreame Technology has released intriguing teasers on its German social media channels ahead of IFA 2025 in Berlin, suggesting significant advances in robotic cleaning capabilities, including potential stair-climbing functionality and robotic arm integration. The Chinese smart home manufacturer, which has rapidly established itself as a major player in the Australian robot vacuum market, shared images ... Read more

    The post IFA 2025: Stair-Climbing Robot Vacuums? Dreame Hints at Game-Changing Tech appeared first on Smart Office.

    ]]>
    Dreame Technology has released intriguing teasers on its German social media channels ahead of IFA 2025 in Berlin, suggesting significant advances in robotic cleaning capabilities, including potential stair-climbing functionality and robotic arm integration.

    The Chinese smart home manufacturer, which has rapidly established itself as a major player in the Australian robot vacuum market, shared images showing a robot vacuum approaching a staircase and another featuring a vacuum equipped with a robotic arm.

    These teasers point to new levels of automation that could address long-standing limitations in current robotic cleaning technology.

    Dreame has scheduled a press conference for September 4, 2025, at 1:00 PM in Hall 7.1A, promising to unveil “the next chapter of Dreame Technology.”

    The company has confirmed the event will showcase:

    • Latest generation robotic vacuums with enhanced capabilities
    • New lawnmowers and floor cleaning systems
    • Entirely new product categories to expand the Dreame ecosystem

    The announcements are positioned to take the company’s smart home technology “to a whole new level,” according to official statements.

    The teased innovations suggest Dreame may be addressing one of the biggest challenges in robotic cleaning, navigating multi-level homes.

    Current robot vacuums in the Australian market, which typically range from $500 to $2,500 for premium models, are limited to single-floor operation and require manual transport between levels.

    A stair-climbing capability would represent a breakthrough for the industry, while robotic arms could enable vacuums to handle tasks like moving lightweight obstacles or reaching previously inaccessible areas.

    Dreame has become increasingly competitive in Australia’s robot vacuum market, offering products that often undercut established brands like iRobot and Ecovacs while matching or exceeding their features.

    The company’s current flagship models retail for around $1,500-$2,000 in Australia, competing directly with premium offerings from rivals.

    Following its ambitious showing at CES 2025 earlier this year, where the company demonstrated its commitment to pushing design boundaries, these IFA announcements could further shake up the local market.

    Australian consumers have shown a strong appetite for advanced cleaning robots, with the category experiencing consistent growth as more households adopt automated cleaning solutions.

    With the press conference just days away, the robotics and smart home industry will be watching closely to see if Dreame can deliver on these ambitious teasers.

    If the company successfully introduces stair-climbing technology or functional robotic arms at consumer-friendly price points, it could trigger a new wave of innovation across the entire category.

    Industry observers, including specialist publication Vacuum Wars, will be monitoring the announcements closely, with full details expected to emerge from the September 4 event in Berlin.

    The post IFA 2025: Stair-Climbing Robot Vacuums? Dreame Hints at Game-Changing Tech appeared first on Smart Office.

    ]]>
    https://smartoffice.com.au/ifa-2025-stair-climbing-robot-vacuums-dreame-hints-at-game-changing-tech/feed/ 0