settings = $settings; add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) ); add_action( 'admin_init', array( $this, 'save_settings' ) ); add_action( 'admin_post_rafgrid_export_xlsx', array( $this, 'download_xlsx' ) ); add_action( 'admin_post_rafgrid_labels', array( $this, 'labels_page' ) ); add_action( 'admin_post_rafgrid_download_export', array( $this, 'download_export' ) ); add_action( 'admin_post_rafgrid_merchant_feed', array( $this, 'merchant_feed' ) ); add_action( 'admin_post_nopriv_rafgrid_merchant_feed', array( $this, 'merchant_feed' ) ); add_filter( 'plugin_action_links_' . plugin_basename( RAFGRID_FILE ), array( $this, 'action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'row_meta' ), 10, 2 ); add_filter( 'admin_footer_text', array( $this, 'footer_text' ) ); } /** * Menu. */ public function menu() { $menu_title = (string) $this->settings->get( 'menu_title', '' ); if ( '' === $menu_title ) { $menu_title = 'Rafgrid'; } add_menu_page( $menu_title, $menu_title, 'read', 'rafgrid', array( $this, 'render_app' ), self::menu_icon(), 30 ); add_submenu_page( 'rafgrid', __( 'Sheets', 'rafgrid' ), __( 'Sheets', 'rafgrid' ), 'read', 'rafgrid', array( $this, 'render_app' ) ); add_submenu_page( 'rafgrid', __( 'Documentation', 'rafgrid' ), __( 'Documentation', 'rafgrid' ), 'read', 'rafgrid-docs', array( $this, 'render_docs' ) ); add_submenu_page( 'rafgrid', __( 'Settings', 'rafgrid' ), __( 'Settings', 'rafgrid' ), 'manage_options', 'rafgrid-settings', array( $this, 'render_settings' ) ); } /** * Admin menu icon: the Rafgrid "R made of cells" mark as a monochrome SVG. * WordPress expects a base64 data URI here and recolors it to match the * admin color scheme. * * @return string */ private static function menu_icon(): string { $svg = ''; return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- add_menu_page() requires a base64 SVG data URI; the SVG source is right above. } /** * Assets for the app screen only. * * @param string $hook Hook. */ public function assets( $hook ) { if ( false !== strpos( $hook, 'rafgrid-settings' ) || false !== strpos( $hook, 'rafgrid-docs' ) ) { wp_enqueue_style( 'rafgrid-fallback', RAFGRID_URL . 'assets/css/admin.css', array(), RAFGRID_VERSION ); if ( false !== strpos( $hook, 'rafgrid-settings' ) ) { wp_enqueue_script( 'rafgrid-settings', RAFGRID_URL . 'assets/js/settings.js', array(), RAFGRID_VERSION, array( 'in_footer' => true ) ); wp_localize_script( 'rafgrid-settings', 'RAFGRID_SETTINGS', array( 'copiedText' => __( 'Copied!', 'rafgrid' ), ) ); } if ( false !== strpos( $hook, 'rafgrid-docs' ) ) { wp_enqueue_script( 'rafgrid-docs', RAFGRID_URL . 'assets/js/docs.js', array(), RAFGRID_VERSION, array( 'in_footer' => true ) ); wp_localize_script( 'rafgrid-docs', 'RAFGRID_DOCS_I18N', array( /* translators: %d: number of search matches in documentation */ 'found' => __( '%d matches', 'rafgrid' ), 'sections' => __( 'sections', 'rafgrid' ), 'none' => __( 'Nothing found. Try different words.', 'rafgrid' ), ) ); } return; } if ( 'toplevel_page_rafgrid' !== $hook ) { return; } if ( ! Access::can_use( $this->settings ) ) { return; } $asset_file = RAFGRID_DIR . 'build/index.asset.php'; $asset = file_exists( $asset_file ) ? require $asset_file : array( 'dependencies' => array( 'wp-element', 'wp-i18n', 'wp-api-fetch', 'wp-components' ), 'version' => RAFGRID_VERSION, ); wp_register_script( 'rafgrid-app', RAFGRID_URL . 'build/index.js', $asset['dependencies'], $asset['version'], array( 'in_footer' => true ) ); wp_register_style( 'rafgrid-app', RAFGRID_URL . 'build/style-index.css', array( 'wp-components' ), $asset['version'] ); // Fallback CSS when the React build is missing (dev without npm). wp_register_style( 'rafgrid-fallback', RAFGRID_URL . 'assets/css/admin.css', array(), RAFGRID_VERSION ); wp_enqueue_media(); if ( file_exists( RAFGRID_DIR . 'build/index.js' ) ) { wp_enqueue_script( 'rafgrid-app' ); wp_enqueue_style( 'rafgrid-app' ); wp_set_script_translations( 'rafgrid-app', 'rafgrid', RAFGRID_DIR . 'languages' ); wp_localize_script( 'rafgrid-app', 'RAFGRID_BOOT', array( 'restUrl' => esc_url_raw( rest_url( 'rafgrid/v1' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'xlsxUrl' => esc_url_raw( admin_url( 'admin-post.php?action=rafgrid_export_xlsx' ) ), 'xlsxNonce' => wp_create_nonce( 'rafgrid_export_xlsx' ), 'labelsUrl' => esc_url_raw( admin_url( 'admin-post.php?action=rafgrid_labels' ) ), 'feedUrl' => esc_url_raw( admin_url( 'admin-post.php?action=rafgrid_merchant_feed&key=' . urlencode( (string) $this->settings->get( 'merchant_feed_key', '' ) ) ) ), ) ); } else { wp_enqueue_style( 'rafgrid-fallback' ); } } /** * App root. */ public function render_app() { if ( ! Access::can_use( $this->settings ) ) { wp_die( esc_html__( 'You do not have permission to use Rafgrid.', 'rafgrid' ) ); } echo '
'; echo esc_html__( 'The interface bundle is not built yet. Run "npm install && npm run build" inside the plugin folder, then reload this page.', 'rafgrid' ); echo '
Plugin language).', 'rafgrid' ); ?>
new with word diff before anything is saved. Duplicate, trash, restore and permanent delete live in the same bar; products also get Labels printing, alt text filling, AI excerpts and image-by-URL import there.', 'rafgrid' ); ?>
Supplier feed URL, CSV with sku,price,stock) updates prices and stock by SKU and can also run daily; running it requires an administrator. The daily scheduled CSV export of the default list is stored as a private file (never publicly reachable) and downloaded from Settings -> Scheduled export. Configuration export/import (Views & history) moves presets, views, column layouts and rules between sites as one JSON file; site settings are included only when an administrator exports, and the AI API key is never exported.', 'rafgrid' ); ?>
, empty, contains) THEN set another column to a value, optionally notifying via the bulk webhook or email. Example: if Stock qty = 0 then set Status to draft. Only administrators create, change and run rules. Hourly runs act as the administrator who saved the rule and manual runs as the administrator who presses Run, so every change passes that person\'s permissions and the normal save validations and lands in the audit log. A rule saved by someone who is no longer an administrator is skipped until an administrator saves it again (this also applies once to rules saved before version 2.0).', 'rafgrid' ); ?>
MCP) exposes %d tools over Streamable HTTP at /wp-json/rafgrid/v1/mcp for Claude, Cursor and other AI clients: querying, cell and bulk edits, variations, terms, comments, orders, snapshots and more. It covers content and catalog work only: there are no tools for users, roles, plugins, themes or site settings, automation rules are managed in wp-admin, and the admin tool group (snapshots, feed sync, background jobs, running rules, AI) works only for administrators. Access control offers Full, Read-only and Custom modes, per-role gates, tool groups (read, write, destructive, admin), an individual tool deny-list, per-user rate limiting and an IP allowlist. Every tool call is logged in Settings. The AI can never exceed the permissions of the WordPress user it authenticates as.', 'rafgrid' ), count( MCP::tools() ) ); ?>
Profile -> Application Passwords, creates a password for their AI client and puts it into the client config as the header "Authorization: Basic BASE64(username:application-password)". The assistant then acts exactly as that WordPress user. Revoking the application password in the profile disconnects the client at once, and security plugins that guard logins and application passwords keep working as usual.', 'rafgrid' ); ?>
wp rafgrid types --user=admin · wp rafgrid bulk --type=product --column=_regular_price --mode=increase_percent --value=10 --ids=1,2,3 --user=admin · wp rafgrid export --type=post --user=admin
Plugin language without changing the rest of the admin; Follow site language uses the site or user language when it is one of these.', 'rafgrid' ); ?>
AI. Without either, nothing is sent anywhere.', 'rafgrid' ); ?>
{
"mcpServers": {
"rafgrid": {
"command": "npx",
"args": ["-y", "mcp-remote", "",
"--header", "Authorization: Basic BASE64(username:application-password)"]
}
}
}
🌐
✉
✈ Telegram
📷 Instagram @
▶ YouTube
|