$data */ public static function textOf(array $data): string { $lines = []; self::walk($data, static function (string &$value) use (&$lines): void { $line = ContentAbilities::readable($value); if ($line !== '') { $lines[] = $line; } }); return mb_substr(implode("\n", $lines), 0, 20000); } /** * Replaces an exact piece of text across every text setting of the page. * * @return array{data?:array,count:int,error?:string} */ public static function replace(int $postId, string $search, string $replace, bool $all, ?Overlay $overlay = null): array { $replace = wp_kses_post($replace); $data = self::data($postId, $overlay); if ($data === null) { return ['count' => 0, 'error' => __('That page is not built with a page builder the assistant can change.', 'sarcio')]; } $count = 0; self::walk($data, static function (string &$value) use ($search, &$count): void { $count += $search === '' ? 0 : substr_count($value, $search); }); if ($count === 0) { return ['count' => 0, 'error' => __('That text was not found on the page. Read the page and copy the text exactly.', 'sarcio')]; } if ($count > 1 && !$all) { return ['count' => $count, 'error' => sprintf( /* translators: %d: how many times the text appears. */ __('That text appears %d times on the page. Say which one, or ask to change all of them.', 'sarcio'), $count, )]; } self::walk($data, static function (string &$value) use ($search, $replace): void { $value = str_replace($search, $replace, $value); }); return ['count' => $count, 'data' => $data]; } /** Elementor's own encoding of the tree, as a value for the meta. */ public static function encode(array $data): string { return (string) wp_json_encode($data); } /** Writes the tree back and clears what Elementor cached for the page. */ public static function write(int $postId, array $data): bool { $stored = update_post_meta($postId, self::ELEMENTOR_DATA, wp_slash(self::encode($data))); if ($stored === false) { return (string) get_post_meta($postId, self::ELEMENTOR_DATA, true) === self::encode($data); } delete_post_meta($postId, '_elementor_css'); delete_post_meta($postId, self::ELEMENTOR_CACHE); if (class_exists('\Elementor\Plugin') && isset(\Elementor\Plugin::$instance->files_manager)) { \Elementor\Plugin::$instance->files_manager->clear_cache(); } return true; } /** @return array|null */ public static function data(int $postId, ?Overlay $overlay = null): ?array { $raw = self::meta($postId, self::ELEMENTOR_DATA, $overlay); if (!is_string($raw) || $raw === '') { return null; } $decoded = json_decode($raw, true); return is_array($decoded) ? $decoded : null; } /** Every text-bearing setting of every element, in page order. */ private static function walk(array &$elements, callable $visit): void { foreach ($elements as &$element) { if (!is_array($element)) { continue; } if (($element['elType'] ?? '') === 'widget' && is_array($element['settings'] ?? null)) { foreach ($element['settings'] as $key => &$value) { if (is_string($value) && self::isText($key, $value)) { $visit($value); } } unset($value); } if (is_array($element['elements'] ?? null)) { self::walk($element['elements'], $visit); } } unset($element); } /** A setting that holds words rather than a size, a colour, a url or an id. */ private static function isText(string $key, string $value): bool { if (trim($value) === '' || preg_match('~^(#[0-9a-f]{3,8}|-?\d+(\.\d+)?(px|em|rem|%|vh|vw)?|https?://\S+|/\S*|[a-z0-9_-]+)$~i', $value)) { return false; } return !preg_match('~(^_|_id$|_size$|_color$|_url$|_class|_icon|_align|_position|_tag$|_type$|^css_|^html_tag$|^link$)~', $key); } private static function meta(int $postId, string $key, ?Overlay $overlay): mixed { if ($overlay !== null && array_key_exists($key, $overlay->meta[$postId] ?? [])) { return $overlay->meta[$postId][$key]; } return get_post_meta($postId, $key, true); } }