| @@ -137,20 +137,22 @@ | ||
| 137 | 137 | $snippet['functionArgs'] = []; |
| 138 | 138 | $snippet['functionArgsDict'] = []; |
| 139 | 139 | $snippet['functionBehavior'] = ''; |
| 140 | 140 | $snippet['functionTarget'] = 'PHP'; |
| 141 | + $snippet['functionMcp'] = false; | |
| 141 | 142 | |
| 142 | 143 | foreach ( $functions_snippet as $function_snippet ) { |
| 143 | 144 | if ( $function_snippet['snippetId'] == $snippetId ) { |
| 144 | 145 | |
| 145 | 146 | $snippet['functionName'] = $function_snippet['name']; |
| 146 | - if ( !isset( $snippet['functionBehavior'] ) || empty( $snippet['functionBehavior'] ) ) { | |
| 147 | + $snippet['functionMcp'] = !empty( $function_snippet['mcp'] ); | |
| 148 | + if ( !isset( $function_snippet['behavior'] ) || empty( $function_snippet['behavior'] ) ) { | |
| 147 | 149 | $snippet['functionBehavior'] = 'dynamic'; |
| 148 | 150 | } |
| 149 | 151 | else { |
| 150 | 152 | $snippet['functionBehavior'] = $function_snippet['behavior']; |
| 151 | 153 | } |
| 152 | - if ( !isset( $snippet['functionTarget'] ) || empty( $snippet['functionTarget'] ) ) { | |
| 154 | + if ( !isset( $function_snippet['target'] ) || empty( $function_snippet['target'] ) ) { | |
| 153 | 155 | $snippet['functionTarget'] = 'PHP'; |
| 154 | 156 | } |
| 155 | 157 | else { |
| 156 | 158 | $snippet['functionTarget'] = $function_snippet['target']; |
| @@ -215,8 +217,9 @@ | ||
| 215 | 217 | 'name' => $params['functionName'], |
| 216 | 218 | 'behavior' => $params['functionBehavior'], |
| 217 | 219 | 'desc' => $params['description'] ?? '', |
| 218 | 220 | 'target' => $params['functionTarget'] ?? 'PHP', |
| 221 | + 'mcp' => !empty( $params['functionMcp'] ), | |
| 219 | 222 | 'args' => [], |
| 220 | 223 | ]; |
| 221 | 224 | |
| 222 | 225 | foreach ( $params['functionArgs'] as $argName ) { |
| @@ -353,8 +356,27 @@ | ||
| 353 | 356 | |
| 354 | 357 | return null; |
| 355 | 358 | } |
| 356 | 359 | |
| 360 | + /** | |
| 361 | + * Return the snippet IDs of every function that opted in to MCP exposure. | |
| 362 | + * Used by the "MCP" list filter and its count (functionMcp lives in the | |
| 363 | + * functions option, not a snippets table column, so it can't be queried in SQL). | |
| 364 | + * | |
| 365 | + * @return int[] | |
| 366 | + */ | |
| 367 | + public function get_mcp_function_ids() | |
| 368 | + { | |
| 369 | + $functions = get_option( $this->option_functions, [] ); | |
| 370 | + $ids = []; | |
| 371 | + foreach ( $functions as $fn ) { | |
| 372 | + if ( !empty( $fn['mcp'] ) && !empty( $fn['snippetId'] ) ) { | |
| 373 | + $ids[] = (int) $fn['snippetId']; | |
| 374 | + } | |
| 375 | + } | |
| 376 | + return $ids; | |
| 377 | + } | |
| 378 | + | |
| 357 | 379 | #endregion |
| 358 | 380 | |
| 359 | 381 | #region Utilities |
| 360 | 382 | |
| @@ -390,8 +412,9 @@ | ||
| 390 | 412 | $scopes = [ |
| 391 | 413 | "global" => "persistent", |
| 392 | 414 | "front-end" => "frontend", |
| 393 | 415 | "admin" => "backend", |
| 416 | + "content" => "content_php", | |
| 394 | 417 | ]; |
| 395 | 418 | |
| 396 | 419 | if ( array_key_exists( $params['scope'], $scopes ) ) { |
| 397 | 420 | $params['scope'] = $scopes[$params['scope']]; |
| @@ -536,9 +559,9 @@ | ||
| 536 | 559 | return $tag !== ''; |
| 537 | 560 | } ); |
| 538 | 561 | } |
| 539 | 562 | $params['tags'] = $tags ? implode( ',', $tags ) : ''; |
| 540 | - $params['code'] = $this->sanitize_code( $params['code'] ); | |
| 563 | + //$params['code'] = $this->sanitize_code( $params['code'] ); | |
| 541 | 564 | return $params; |
| 542 | 565 | } |
| 543 | 566 | |
| 544 | 567 | /** |
| @@ -585,9 +608,15 @@ | ||
| 585 | 608 | $stats[$scope] = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT( * ) FROM $this->table_name WHERE scope = %s", $scope ) ); |
| 586 | 609 | } |
| 587 | 610 | $stats['all'] += $stats[$scope]; |
| 588 | 611 | } |
| 589 | - | |
| 612 | + | |
| 613 | + // Combined count for the "Content" filter (PHP + JS). Not added to 'all', already counted above. | |
| 614 | + $stats['content'] = (int) $stats['content_php'] + (int) $stats['content_js']; | |
| 615 | + | |
| 616 | + // Functions exposed via MCP. A subset of 'function', so not added to 'all'. | |
| 617 | + $stats['mcp'] = count( $this->get_mcp_function_ids() ); | |
| 618 | + | |
| 590 | 619 | return $stats; |
| 591 | 620 | } |
| 592 | 621 | |
| 593 | 622 | public function import( ) |
| @@ -610,15 +639,34 @@ | ||
| 610 | 639 | |
| 611 | 640 | return count( $snippets ); |
| 612 | 641 | } |
| 613 | 642 | |
| 614 | - private function sanitize_code( $code ) | |
| 643 | + public function sanitize_code( $code ) | |
| 615 | 644 | { |
| 616 | - $code = preg_replace( '/<\?php/', '', $code, 1 ); | |
| 617 | 645 | $code = ltrim( $code ); |
| 646 | + | |
| 647 | + $first_chats = substr( $code, 0, 5 ); | |
| 648 | + if( $first_chats === '<?php' ) { | |
| 649 | + $code = substr( $code, 5 ); | |
| 650 | + $code = ltrim( $code ); | |
| 651 | + } | |
| 652 | + | |
| 618 | 653 | return $code; |
| 619 | 654 | } |
| 620 | 655 | |
| 656 | + public function delete_duplicates() { | |
| 657 | + // Delete snippets that have the same code and scope, keeping only the most recent one ( based on the updated column ) | |
| 658 | + $query = "DELETE t1 FROM $this->table_name t1 | |
| 659 | + INNER JOIN $this->table_name t2 | |
| 660 | + WHERE t1.id < t2.id | |
| 661 | + AND t1.code = t2.code | |
| 662 | + AND t1.scope = t2.scope"; | |
| 663 | + | |
| 664 | + $this->wpdb->query( $query ); | |
| 665 | + | |
| 666 | + return $this->wpdb->rows_affected; | |
| 667 | + } | |
| 668 | + | |
| 621 | 669 | #endregion |
| 622 | 670 | |
| 623 | 671 | #region Snippet CRUD |
| 624 | 672 | |
| @@ -649,8 +697,11 @@ | ||
| 649 | 697 | } |
| 650 | 698 | else if ( $filter['accessor'] === 'scope' && $filter['value'] === 'global' ) { |
| 651 | 699 | $freshFilters[] = [ 'accessor' => 'scope', 'value' => ['backend', 'frontend', 'persistent'] ]; |
| 652 | 700 | } |
| 701 | + else if ( $filter['accessor'] === 'scope' && $filter['value'] === 'content' ) { | |
| 702 | + $freshFilters[] = [ 'accessor' => 'scope', 'value' => ['content_php', 'content_js'] ]; | |
| 703 | + } | |
| 653 | 704 | else { |
| 654 | 705 | $freshFilters[] = $filter; |
| 655 | 706 | } |
| 656 | 707 | } |
| @@ -682,9 +733,17 @@ | ||
| 682 | 733 | $where[] = $this->wpdb->prepare( "active = %d", $value ); |
| 683 | 734 | } elseif ( $filter['accessor'] === 'endpoint' ) { |
| 684 | 735 | $where[] = boolval( $filter['value'] ) ? "endpoint <> ''" : "endpoint = ''"; |
| 685 | 736 | } elseif ( $filter['accessor'] === 'scope' ) { |
| 686 | - if ( is_array( $filter['value'] ) ) { | |
| 737 | + if ( $filter['value'] === 'mcp' ) { | |
| 738 | + // Not a scope: constrain to the functions opted in to MCP exposure. | |
| 739 | + $ids = $this->get_mcp_function_ids(); | |
| 740 | + if ( empty( $ids ) ) { | |
| 741 | + $where[] = '1 = 0'; | |
| 742 | + } else { | |
| 743 | + $where[] = 'id IN (' . implode( ',', array_map( 'intval', $ids ) ) . ')'; | |
| 744 | + } | |
| 745 | + } else if ( is_array( $filter['value'] ) ) { | |
| 687 | 746 | $scopes = array_map( function( $scope ) { |
| 688 | 747 | return esc_sql( $scope ); |
| 689 | 748 | }, $filter['value'] ); |
| 690 | 749 | $where[] = "scope IN ('" . implode( "', '", $scopes ) . "')"; |
| @@ -874,8 +933,12 @@ | ||
| 874 | 933 | } |
| 875 | 934 | |
| 876 | 935 | function check_db( ) |
| 877 | 936 | { |
| 937 | + if ( $this->db_check ) { | |
| 938 | + return true; | |
| 939 | + } | |
| 940 | + | |
| 878 | 941 | if ( $this->does_table_exist( $this->table_name ) ) { |
| 879 | 942 | $this->check_columns( ); |
| 880 | 943 | $this->db_check = true; |
| 881 | 944 | } else { |