prefix . 'aieo_content_text'; } public static function post_types() { return apply_filters('aieo_content_text_post_types', array('product', 'post', 'page')); } public static function ensure_table() { global $wpdb; $table = self::table(); $charset_collate = aieo_get_charset_collate(); $wpdb->query("CREATE TABLE IF NOT EXISTS {$table} ( `object_type` varchar(32) NOT NULL, `object_id` bigint unsigned NOT NULL, `text_hash` char(32) NOT NULL DEFAULT '', `text_content` mediumtext NULL, `word_count` int unsigned NOT NULL DEFAULT 0, `source_modified` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`object_type`, `object_id`), KEY `updated_at` (`updated_at`) ) {$charset_collate}"); } public static function maybe_install() { if (self::$schema_checked) { return; } if (get_option('aieo_content_text_schema_version') !== self::SCHEMA_VERSION && AIEO_Schema_Lock::acquire('aieo_content_text_schema_version')) { self::ensure_table(); update_option('aieo_content_text_schema_version', self::SCHEMA_VERSION, false); } self::$schema_checked = true; } public static function write_row($object_type, $object_id, $text, $source_modified = null) { global $wpdb; self::maybe_install(); $text = (string) $text; $words = preg_split('/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY); if (!is_array($words)) { $words = preg_split('/\s+/', $text, -1, PREG_SPLIT_NO_EMPTY); } return false !== $wpdb->replace(self::table(), array( 'object_type' => (string) $object_type, 'object_id' => (int) $object_id, 'text_hash' => md5($text), 'text_content' => $text, 'word_count' => $text === '' ? 0 : count((array) $words), 'source_modified' => $source_modified, 'updated_at' => current_time('mysql', true), )); } public static function refresh($batch = 300) { global $wpdb; self::maybe_install(); $table = self::table(); if (false !== get_option(self::CURSOR_OPTION, false)) { if (null === $wpdb->get_var("SELECT 1 FROM {$table} LIMIT 1")) { self::reseed(); } } $cursor = get_option(self::CURSOR_OPTION, array('modified' => '1970-01-01 00:00:00', 'id' => 0)); $modified = isset($cursor['modified']) ? (string) $cursor['modified'] : '1970-01-01 00:00:00'; $after_id = isset($cursor['id']) ? (int) $cursor['id'] : 0; $types = self::post_types(); $placeholders = implode(',', array_fill(0, count($types), '%s')); $rows = $wpdb->get_results($wpdb->prepare( "SELECT ID, post_type, post_content, post_modified FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ({$placeholders}) AND (post_modified > %s OR (post_modified = %s AND ID > %d)) ORDER BY post_modified ASC, ID ASC LIMIT %d", array_merge($types, array($modified, $modified, $after_id, $batch)) )); $scanned = count($rows); $written = 0; if ($scanned > 0) { $keys = array(); foreach ($rows as $r) { $keys[] = $wpdb->prepare('(%s,%d)', $r->post_type, (int) $r->ID); } $existing_rows = $wpdb->get_results( "SELECT object_type, object_id, text_hash FROM {$table} WHERE (object_type, object_id) IN (" . implode(',', $keys) . ')' ); $existing = array(); foreach ($existing_rows as $e) { $existing[$e->object_type . ':' . $e->object_id] = $e->text_hash; } $last_ok = null; $error = ''; foreach ($rows as $r) { $text = self::strip((string) $r->post_content); $hash = md5($text); $key = $r->post_type . ':' . $r->ID; if (isset($existing[$key]) && $existing[$key] === $hash) { $last_ok = $r; continue; } if (!self::write_row($r->post_type, (int) $r->ID, $text, $r->post_modified)) { $error = (string) $wpdb->last_error; break; } $written++; $last_ok = $r; } if (null !== $last_ok) { update_option(self::CURSOR_OPTION, array( 'modified' => $last_ok->post_modified, 'id' => (int) $last_ok->ID, ), false); } if ('' !== $error) { return array('scanned' => $scanned, 'written' => $written, 'done' => true, 'error' => $error); } } $done = $scanned < $batch; if ($done) { self::gc(); } return array('scanned' => $scanned, 'written' => $written, 'done' => $done); } public static function gc() { global $wpdb; $table = self::table(); return (int) $wpdb->query( "DELETE ct FROM {$table} ct LEFT JOIN {$wpdb->posts} p ON p.ID = ct.object_id AND p.post_type = ct.object_type WHERE p.ID IS NULL OR p.post_status <> 'publish'" ); } public static function reseed() { delete_option(self::CURSOR_OPTION); } public static function strip($html) { if ($html === '') { return ''; } $text = strip_shortcodes($html); $text = preg_replace('/\[\/?[a-zA-Z0-9_\-]+[^\]\n]{0,200}\]/u', ' ', $text); $text = preg_replace('/<(\/?)(p|div|br|li|tr|td|th|h[1-6]|section|article|blockquote)\b[^>]*>/iu', ' ', $text); $text = wp_strip_all_tags($text, false); $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $text = preg_replace('/[\x{00A0}\x{2000}-\x{200B}]/u', ' ', $text); $text = preg_replace('/\s+/u', ' ', trim($text)); return $text; } public static function get_row($object_type, $object_id) { global $wpdb; self::maybe_install(); $table = self::table(); return $wpdb->get_row($wpdb->prepare( "SELECT text_hash, text_content, word_count, updated_at FROM {$table} WHERE object_type = %s AND object_id = %d", $object_type, $object_id )); } }