prefix . 'abency_proposals'; } /** * Crea la taula de propostes pendents (en activar). */ public static function install_table() { global $wpdb; $table = self::table(); $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$table} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, abency_proposal_id BIGINT UNSIGNED NOT NULL, wp_post_id BIGINT UNSIGNED NOT NULL, post_type VARCHAR(20) NOT NULL DEFAULT 'posts', fields LONGTEXT NOT NULL, base_snapshot LONGTEXT NULL, callback_url TEXT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), KEY abency_proposal_id (abency_proposal_id), KEY status (status) ) {$charset_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql); } /** * Desa una proposta entrant. Retorna la referència local (remote_ref). * * @param array $data Payload validat de l'endpoint REST. * @return int ID local de la proposta. */ public static function store(array $data) { global $wpdb; $now = current_time('mysql', true); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Insert into the plugin's own custom table. $wpdb->insert( self::table(), array( 'abency_proposal_id' => (int) $data['proposal_id'], 'wp_post_id' => (int) $data['wp_post_id'], 'post_type' => sanitize_text_field($data['post_type']), 'fields' => wp_json_encode($data['fields']), 'base_snapshot' => isset($data['base_snapshot']) ? wp_json_encode($data['base_snapshot']) : null, 'callback_url' => isset($data['callback_url']) ? esc_url_raw($data['callback_url']) : null, 'status' => self::STATUS_PENDING, 'created_at' => $now, 'updated_at' => $now, ), array('%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ); return (int) $wpdb->insert_id; } public static function get($id) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read the plugin's own custom table by primary key. $row = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}abency_proposals WHERE id = %d", $id), ARRAY_A ); if ($row) { $row['fields'] = json_decode($row['fields'], true); $row['base_snapshot'] = $row['base_snapshot'] ? json_decode($row['base_snapshot'], true) : null; } return $row; } /** * @return array> */ public static function pending() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read the plugin's own custom table for the admin queue. $rows = $wpdb->get_results( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}abency_proposals WHERE status = %s ORDER BY created_at DESC", self::STATUS_PENDING), ARRAY_A ); foreach ($rows as &$row) { $row['fields'] = json_decode($row['fields'], true); } return $rows; } /** * Aprova una proposta: fusiona els camps sobre el post ORIGINAL (mateix ID, * URL i menús) i notifica Abency (status=applied). * * @return true|WP_Error */ public static function approve($id) { $proposal = self::get($id); if (! $proposal || self::STATUS_PENDING !== $proposal['status']) { return new WP_Error('abency_invalid', __('This proposal is invalid or has already been processed.', 'abency-connector')); } $post = Abency_Content::checked_post($proposal['wp_post_id'], $proposal['post_type']); if (is_wp_error($post)) { return $post; } $type = get_post_type_object($post->post_type); if (! current_user_can('edit_post', $post->ID) || ! current_user_can($type->cap->publish_posts)) { return new WP_Error('abency_forbidden', 'No tens permís per aprovar aquest contingut.'); } if ('publish' !== $post->post_status) { return new WP_Error('abency_invalid_status', 'El contingut ja no està publicat. Revisa la proposta.'); } $fields = $proposal['fields']; $post_id = (int) $proposal['wp_post_id']; $postarr = array('ID' => $post_id); if (isset($fields['title'])) { $postarr['post_title'] = wp_kses_post($fields['title']); } if (isset($fields['content'])) { $postarr['post_content'] = wp_kses_post($fields['content']); } if (isset($fields['excerpt'])) { $postarr['post_excerpt'] = wp_kses_post($fields['excerpt']); } if (isset($fields['slug'])) { $postarr['post_name'] = sanitize_title($fields['slug']); } $result = wp_update_post($postarr, true); if (is_wp_error($result)) { return $result; } if (! empty($fields['categories']) && is_array($fields['categories'])) { wp_set_post_terms($post_id, array_map('intval', $fields['categories']), 'category'); } if (! empty($fields['tags']) && is_array($fields['tags'])) { wp_set_post_terms($post_id, array_map('intval', $fields['tags']), 'post_tag'); } if (! empty($fields['seo']) && is_array($fields['seo'])) { Abency_Seo::write($post_id, $fields['seo']); } self::set_status($id, self::STATUS_APPLIED); self::notify_abency($proposal, 'applied'); return true; } /** * Rebutja una proposta i ho notifica a Abency (status=rejected). * * @return true|WP_Error */ public static function reject($id, $note = '') { $proposal = self::get($id); if (! $proposal || self::STATUS_PENDING !== $proposal['status']) { return new WP_Error('abency_invalid', __('This proposal is invalid or has already been processed.', 'abency-connector')); } if (! current_user_can('edit_post', (int) $proposal['wp_post_id'])) { return new WP_Error('abency_forbidden', 'No tens permís per rebutjar aquest contingut.'); } self::set_status($id, self::STATUS_REJECTED); self::notify_abency($proposal, 'rejected', $note); return true; } private static function set_status($id, $status) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Update a row in the plugin's own custom table. $wpdb->update( self::table(), array('status' => $status, 'updated_at' => current_time('mysql', true)), array('id' => $id), array('%s', '%s'), array('%d') ); } /** * Notifica l'estat a Abency (WP → Abency) amb el token pinned a la brand. */ private static function notify_abency(array $proposal, $status, $note = '') { $callback = $proposal['callback_url']; $token = Abency_Settings::get(Abency_Settings::OPT_API_TOKEN); if (! $callback || ! $token) { return; } wp_remote_post($callback, array( 'timeout' => 15, 'headers' => array( 'Authorization' => 'Bearer ' . $token, 'Accept' => 'application/json', 'Content-Type' => 'application/json', ), 'body' => wp_json_encode(array( 'status' => $status, 'note' => $note, )), )); } }