Changeset 3712399
- Timestamp:
- 09/25/2026 05:08:26 AM (4 weeks ago)
- Location:
- mountdev-ai-mcp-connector
- Files:
-
- 2 added
- 10 edited
- 1 copied
-
tags/1.8.0 (copied) (copied from mountdev-ai-mcp-connector/trunk )
-
tags/1.8.0/bridge/README.md (added)
-
tags/1.8.0/includes/admin/views/settings.php (modified) (1 diff)
-
tags/1.8.0/includes/class-profile-manager.php (modified) (3 diffs)
-
tags/1.8.0/includes/tools/class-post-tools.php (modified) (3 diffs)
-
tags/1.8.0/mountdev-ai-mcp-connector.php (modified) (2 diffs)
-
tags/1.8.0/readme.txt (modified) (3 diffs)
-
trunk/bridge/README.md (added)
-
trunk/includes/admin/views/settings.php (modified) (1 diff)
-
trunk/includes/class-profile-manager.php (modified) (3 diffs)
-
trunk/includes/tools/class-post-tools.php (modified) (3 diffs)
-
trunk/mountdev-ai-mcp-connector.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
mountdev-ai-mcp-connector/tags/1.8.0/includes/admin/views/settings.php
r3705477 r3712399 804 804 <p><?php esc_html_e( 'Cascadia Web Services looks after WordPress for businesses. This plugin is free, and stays free.', 'mountdev-ai-mcp-connector' ); ?></p> 805 805 <ul> 806 <li><a href="https://cascadiawebservices.com/services/ maintenance/" target="_blank" rel="noopener"><?php esc_html_e( 'WordPress care', 'mountdev-ai-mcp-connector' ); ?></a></li>807 <li><a href="https://cascadiawebservices.com/services/ hosting/" target="_blank" rel="noopener"><?php esc_html_e( 'Hosting', 'mountdev-ai-mcp-connector' ); ?></a></li>806 <li><a href="https://cascadiawebservices.com/services/wordpress/maintenance/" target="_blank" rel="noopener"><?php esc_html_e( 'WordPress care', 'mountdev-ai-mcp-connector' ); ?></a></li> 807 <li><a href="https://cascadiawebservices.com/services/wordpress/hosting/" target="_blank" rel="noopener"><?php esc_html_e( 'Hosting', 'mountdev-ai-mcp-connector' ); ?></a></li> 808 808 <li><a href="https://cascadiawebservices.com/services/email/" target="_blank" rel="noopener"><?php esc_html_e( 'Email delivery', 'mountdev-ai-mcp-connector' ); ?></a></li> 809 <li><a href="https://cascadiawebservices.com/services/a utomation/" target="_blank" rel="noopener"><?php esc_html_e( 'Automation', 'mountdev-ai-mcp-connector' ); ?></a></li>809 <li><a href="https://cascadiawebservices.com/services/ai/business-process-automation/" target="_blank" rel="noopener"><?php esc_html_e( 'Automation', 'mountdev-ai-mcp-connector' ); ?></a></li> 810 810 </ul> 811 811 </div> -
mountdev-ai-mcp-connector/tags/1.8.0/includes/class-profile-manager.php
r3705294 r3712399 63 63 'create_post', 64 64 'update_post', 65 'duplicate_post', 65 66 'delete_post', 66 67 'search_posts', … … 92 93 'create_post', 93 94 'update_post', 95 'duplicate_post', 94 96 'delete_post', 95 97 'search_posts', … … 178 180 'create_post', 179 181 'update_post', 182 'duplicate_post', 180 183 'delete_post', 181 184 'search_posts', -
mountdev-ai-mcp-connector/tags/1.8.0/includes/tools/class-post-tools.php
r3677846 r3712399 14 14 class Post_Tools extends Base_Tool { 15 15 16 /** 17 * Metadata keys never carried over by duplicate_post(). 18 * 19 * Each identifies the original post rather than describing its content: an 20 * editing lock, the slug history a redirect depends on, a canonical URL that 21 * would point the copy at the page it came from, or trash bookkeeping. 22 */ 23 const DUPLICATE_SKIPPED_META = array( 24 '_edit_lock', 25 '_edit_last', 26 '_wp_old_slug', 27 '_wp_old_date', 28 '_wp_trash_meta_status', 29 '_wp_trash_meta_time', 30 '_yoast_wpseo_canonical', 31 'rank_math_canonical_url', 32 ); 33 16 34 public function get_tools() { 17 35 return array( … … 119 137 ), 120 138 'required' => array( 'title', 'content' ), 139 ), 140 ), 141 array( 142 'name' => 'duplicate_post', 143 'description' => 'Duplicate a post, page or other public post type as a new draft', 144 'capability' => 'edit_posts', 145 'callback' => array( $this, 'duplicate_post' ), 146 'input_schema' => array( 147 'type' => 'object', 148 'properties' => array( 149 'post_id' => array( 150 'type' => 'integer', 151 'description' => 'ID of the post or page to copy', 152 ), 153 'title' => array( 154 'type' => 'string', 155 'description' => 'Title for the copy. Defaults to the original title followed by (copy)', 156 ), 157 ), 158 'required' => array( 'post_id' ), 121 159 ), 122 160 ), … … 573 611 'post' => $this->format_post( $post ), 574 612 ); 613 } 614 615 /** 616 * Duplicate a post, page or other public post type as a draft. 617 * 618 * Two gates, both on the caller: edit rights over the post being copied, and 619 * the right to create that post type. Edit rights on the source are what make 620 * copying its protected metadata reasonable, since the caller could already 621 * read and change every one of those values on the original. 622 * 623 * The copy is always a draft, authored by whoever asked for it. That keeps a 624 * user without `publish_posts` from publishing by copying something already 625 * published, and keeps authorship out of `edit_others_posts` territory. 626 * 627 * Metadata is copied key by key rather than wholesale. Builder layouts live 628 * in protected keys (`_elementor_data`, Divi and WPBakery shortcodes, the 629 * page template, the featured image), so a copy that skipped them would look 630 * nothing like the page it came from. The deny list holds the keys that 631 * identify the original rather than describe it: copying those would make 632 * the duplicate claim to be the page it was copied from. 633 * 634 * @param array $args Tool arguments. 635 * @return array|\WP_Error 636 */ 637 public function duplicate_post( $args ) { 638 if ( ! isset( $args['post_id'] ) ) { 639 return new \WP_Error( 'missing_post_id', __( 'Post ID is required.', 'mountdev-ai-mcp-connector' ) ); 640 } 641 642 $post = $this->auth()->get_post_object( $args['post_id'] ); 643 644 if ( is_wp_error( $post ) ) { 645 return $post; 646 } 647 648 $edit_check = $this->auth()->can_edit_post( $post ); 649 if ( is_wp_error( $edit_check ) ) { 650 return $edit_check; 651 } 652 653 $can_create = $this->auth()->can_create_post( $post->post_type, 'draft', 0 ); 654 if ( is_wp_error( $can_create ) ) { 655 return $can_create; 656 } 657 658 $title = isset( $args['title'] ) && '' !== trim( (string) $args['title'] ) 659 ? sanitize_text_field( $args['title'] ) 660 : sprintf( 661 /* translators: %s: Title of the content being copied */ 662 __( '%s (copy)', 'mountdev-ai-mcp-connector' ), 663 $post->post_title 664 ); 665 666 $copy_id = wp_insert_post( 667 array( 668 'post_title' => $title, 669 'post_content' => $post->post_content, 670 'post_excerpt' => $post->post_excerpt, 671 'post_status' => 'draft', 672 'post_type' => $post->post_type, 673 'post_parent' => $post->post_parent, 674 'menu_order' => $post->menu_order, 675 'comment_status' => $post->comment_status, 676 'ping_status' => $post->ping_status, 677 'post_name' => '', 678 ), 679 true 680 ); 681 682 if ( is_wp_error( $copy_id ) ) { 683 return $copy_id; 684 } 685 686 foreach ( get_post_meta( $post->ID ) as $meta_key => $values ) { 687 if ( in_array( $meta_key, self::DUPLICATE_SKIPPED_META, true ) ) { 688 continue; 689 } 690 691 foreach ( $values as $value ) { 692 // Stored values come back serialized and unslashed. Unserialize so 693 // builder data such as Elementor's is written as the array it was, 694 // and re-slash it, because add_post_meta() unslashes whatever it is 695 // given and would otherwise eat the escapes inside builder JSON. 696 add_post_meta( $copy_id, $meta_key, wp_slash( maybe_unserialize( $value ) ) ); 697 } 698 } 699 700 $skipped_taxonomies = array(); 701 702 foreach ( get_object_taxonomies( $post->post_type ) as $taxonomy ) { 703 $term_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) ); 704 705 if ( is_wp_error( $term_ids ) || empty( $term_ids ) ) { 706 continue; 707 } 708 709 if ( is_wp_error( $this->auth()->can_assign_terms( $copy_id, $taxonomy ) ) ) { 710 $skipped_taxonomies[] = $taxonomy; 711 continue; 712 } 713 714 wp_set_object_terms( $copy_id, $term_ids, $taxonomy ); 715 } 716 717 $result = array( 718 'success' => true, 719 'message' => __( 'Copy created as a draft.', 'mountdev-ai-mcp-connector' ), 720 'source_id' => $post->ID, 721 'post' => $this->format_post( get_post( $copy_id ) ), 722 ); 723 724 if ( ! empty( $skipped_taxonomies ) ) { 725 $result['skipped_taxonomies'] = $skipped_taxonomies; 726 } 727 728 return $result; 575 729 } 576 730 -
mountdev-ai-mcp-connector/tags/1.8.0/mountdev-ai-mcp-connector.php
r3705477 r3712399 4 4 * Plugin URI: https://cascadiaweb.services/products/mountdev/easy-mcp-connector 5 5 * Description: Transform your WordPress site into an AI-powered Model Context Protocol (MCP) server. Exposes WordPress functionality through a standardized interface for AI agents. 6 * Version: 1. 7.16 * Version: 1.8.0 7 7 * Requires at least: 5.8 8 8 * Requires PHP: 7.4 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'MOUNTDEV_AI_MCP_CONNECTOR_VERSION', '1. 7.1' );22 define( 'MOUNTDEV_AI_MCP_CONNECTOR_VERSION', '1.8.0' ); 23 23 define( 'MOUNTDEV_AI_MCP_CONNECTOR_PLUGIN_FILE', __FILE__ ); 24 24 define( 'MOUNTDEV_AI_MCP_CONNECTOR_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
mountdev-ai-mcp-connector/tags/1.8.0/readme.txt
r3705489 r3712399 5 5 Tested up to: 7.1.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 7.17 Stable tag: 1.8.0 8 8 License: GPLv3 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.en.html 10 10 11 Connect ChatGPT or Claude to WordPress with one address, no API keys. Your assistant can write posts, update products and fix SEO, within limits you set.11 Connect ChatGPT or Claude to WordPress with one address, no API keys. Your assistant can write posts, update products and fix SEO, within your limits. 12 12 13 13 == Description == … … 39 39 * Any client that speaks the Model Context Protocol 40 40 41 = 38 8tools, across the plugins you already run =42 43 8 1WordPress core, 78 Rank Math SEO, 74 WooCommerce, 71 Elementor (36 free and 35 Pro), 35 Jetpack, 29 Yoast SEO and 20 Contact Form 7. Tools for a plugin appear only when that plugin is active, so the list stays as short as your site is.41 = 389 tools, across the plugins you already run = 42 43 82 WordPress core, 78 Rank Math SEO, 74 WooCommerce, 71 Elementor (36 free and 35 Pro), 35 Jetpack, 29 Yoast SEO and 20 Contact Form 7. Tools for a plugin appear only when that plugin is active, so the list stays as short as your site is. 44 44 45 45 Written for the specific plugin, not guessed at. Rank Math's focus keywords, Elementor's page structure and WooCommerce's variations each store data their own way, and each tool is built for how that plugin actually works. … … 130 130 131 131 == Changelog == 132 133 = 1.8.0 = 134 135 * Added: duplicate_post copies a post, page or other public post type as a new draft. The copy keeps the content, the layout as your builder stored it, the featured image, the page template and the terms, and is titled "(copy)" unless you give it a title. It is always a draft, and it is refused unless you may edit what you are copying 136 * Fixed: Three links in the "Who builds this" box on the settings page (WordPress care, Hosting and Automation) led to pages that no longer exist. They now open the current pages 137 * Fixed: bridge/README.md is included in the plugin again. 1.7.1 left it out by mistake 138 * Changed: The short description is now within WordPress.org's 150 character limit, so the directory no longer cuts it off 132 139 133 140 = 1.7.1 = -
mountdev-ai-mcp-connector/trunk/includes/admin/views/settings.php
r3705477 r3712399 804 804 <p><?php esc_html_e( 'Cascadia Web Services looks after WordPress for businesses. This plugin is free, and stays free.', 'mountdev-ai-mcp-connector' ); ?></p> 805 805 <ul> 806 <li><a href="https://cascadiawebservices.com/services/ maintenance/" target="_blank" rel="noopener"><?php esc_html_e( 'WordPress care', 'mountdev-ai-mcp-connector' ); ?></a></li>807 <li><a href="https://cascadiawebservices.com/services/ hosting/" target="_blank" rel="noopener"><?php esc_html_e( 'Hosting', 'mountdev-ai-mcp-connector' ); ?></a></li>806 <li><a href="https://cascadiawebservices.com/services/wordpress/maintenance/" target="_blank" rel="noopener"><?php esc_html_e( 'WordPress care', 'mountdev-ai-mcp-connector' ); ?></a></li> 807 <li><a href="https://cascadiawebservices.com/services/wordpress/hosting/" target="_blank" rel="noopener"><?php esc_html_e( 'Hosting', 'mountdev-ai-mcp-connector' ); ?></a></li> 808 808 <li><a href="https://cascadiawebservices.com/services/email/" target="_blank" rel="noopener"><?php esc_html_e( 'Email delivery', 'mountdev-ai-mcp-connector' ); ?></a></li> 809 <li><a href="https://cascadiawebservices.com/services/a utomation/" target="_blank" rel="noopener"><?php esc_html_e( 'Automation', 'mountdev-ai-mcp-connector' ); ?></a></li>809 <li><a href="https://cascadiawebservices.com/services/ai/business-process-automation/" target="_blank" rel="noopener"><?php esc_html_e( 'Automation', 'mountdev-ai-mcp-connector' ); ?></a></li> 810 810 </ul> 811 811 </div> -
mountdev-ai-mcp-connector/trunk/includes/class-profile-manager.php
r3705294 r3712399 63 63 'create_post', 64 64 'update_post', 65 'duplicate_post', 65 66 'delete_post', 66 67 'search_posts', … … 92 93 'create_post', 93 94 'update_post', 95 'duplicate_post', 94 96 'delete_post', 95 97 'search_posts', … … 178 180 'create_post', 179 181 'update_post', 182 'duplicate_post', 180 183 'delete_post', 181 184 'search_posts', -
mountdev-ai-mcp-connector/trunk/includes/tools/class-post-tools.php
r3677846 r3712399 14 14 class Post_Tools extends Base_Tool { 15 15 16 /** 17 * Metadata keys never carried over by duplicate_post(). 18 * 19 * Each identifies the original post rather than describing its content: an 20 * editing lock, the slug history a redirect depends on, a canonical URL that 21 * would point the copy at the page it came from, or trash bookkeeping. 22 */ 23 const DUPLICATE_SKIPPED_META = array( 24 '_edit_lock', 25 '_edit_last', 26 '_wp_old_slug', 27 '_wp_old_date', 28 '_wp_trash_meta_status', 29 '_wp_trash_meta_time', 30 '_yoast_wpseo_canonical', 31 'rank_math_canonical_url', 32 ); 33 16 34 public function get_tools() { 17 35 return array( … … 119 137 ), 120 138 'required' => array( 'title', 'content' ), 139 ), 140 ), 141 array( 142 'name' => 'duplicate_post', 143 'description' => 'Duplicate a post, page or other public post type as a new draft', 144 'capability' => 'edit_posts', 145 'callback' => array( $this, 'duplicate_post' ), 146 'input_schema' => array( 147 'type' => 'object', 148 'properties' => array( 149 'post_id' => array( 150 'type' => 'integer', 151 'description' => 'ID of the post or page to copy', 152 ), 153 'title' => array( 154 'type' => 'string', 155 'description' => 'Title for the copy. Defaults to the original title followed by (copy)', 156 ), 157 ), 158 'required' => array( 'post_id' ), 121 159 ), 122 160 ), … … 573 611 'post' => $this->format_post( $post ), 574 612 ); 613 } 614 615 /** 616 * Duplicate a post, page or other public post type as a draft. 617 * 618 * Two gates, both on the caller: edit rights over the post being copied, and 619 * the right to create that post type. Edit rights on the source are what make 620 * copying its protected metadata reasonable, since the caller could already 621 * read and change every one of those values on the original. 622 * 623 * The copy is always a draft, authored by whoever asked for it. That keeps a 624 * user without `publish_posts` from publishing by copying something already 625 * published, and keeps authorship out of `edit_others_posts` territory. 626 * 627 * Metadata is copied key by key rather than wholesale. Builder layouts live 628 * in protected keys (`_elementor_data`, Divi and WPBakery shortcodes, the 629 * page template, the featured image), so a copy that skipped them would look 630 * nothing like the page it came from. The deny list holds the keys that 631 * identify the original rather than describe it: copying those would make 632 * the duplicate claim to be the page it was copied from. 633 * 634 * @param array $args Tool arguments. 635 * @return array|\WP_Error 636 */ 637 public function duplicate_post( $args ) { 638 if ( ! isset( $args['post_id'] ) ) { 639 return new \WP_Error( 'missing_post_id', __( 'Post ID is required.', 'mountdev-ai-mcp-connector' ) ); 640 } 641 642 $post = $this->auth()->get_post_object( $args['post_id'] ); 643 644 if ( is_wp_error( $post ) ) { 645 return $post; 646 } 647 648 $edit_check = $this->auth()->can_edit_post( $post ); 649 if ( is_wp_error( $edit_check ) ) { 650 return $edit_check; 651 } 652 653 $can_create = $this->auth()->can_create_post( $post->post_type, 'draft', 0 ); 654 if ( is_wp_error( $can_create ) ) { 655 return $can_create; 656 } 657 658 $title = isset( $args['title'] ) && '' !== trim( (string) $args['title'] ) 659 ? sanitize_text_field( $args['title'] ) 660 : sprintf( 661 /* translators: %s: Title of the content being copied */ 662 __( '%s (copy)', 'mountdev-ai-mcp-connector' ), 663 $post->post_title 664 ); 665 666 $copy_id = wp_insert_post( 667 array( 668 'post_title' => $title, 669 'post_content' => $post->post_content, 670 'post_excerpt' => $post->post_excerpt, 671 'post_status' => 'draft', 672 'post_type' => $post->post_type, 673 'post_parent' => $post->post_parent, 674 'menu_order' => $post->menu_order, 675 'comment_status' => $post->comment_status, 676 'ping_status' => $post->ping_status, 677 'post_name' => '', 678 ), 679 true 680 ); 681 682 if ( is_wp_error( $copy_id ) ) { 683 return $copy_id; 684 } 685 686 foreach ( get_post_meta( $post->ID ) as $meta_key => $values ) { 687 if ( in_array( $meta_key, self::DUPLICATE_SKIPPED_META, true ) ) { 688 continue; 689 } 690 691 foreach ( $values as $value ) { 692 // Stored values come back serialized and unslashed. Unserialize so 693 // builder data such as Elementor's is written as the array it was, 694 // and re-slash it, because add_post_meta() unslashes whatever it is 695 // given and would otherwise eat the escapes inside builder JSON. 696 add_post_meta( $copy_id, $meta_key, wp_slash( maybe_unserialize( $value ) ) ); 697 } 698 } 699 700 $skipped_taxonomies = array(); 701 702 foreach ( get_object_taxonomies( $post->post_type ) as $taxonomy ) { 703 $term_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) ); 704 705 if ( is_wp_error( $term_ids ) || empty( $term_ids ) ) { 706 continue; 707 } 708 709 if ( is_wp_error( $this->auth()->can_assign_terms( $copy_id, $taxonomy ) ) ) { 710 $skipped_taxonomies[] = $taxonomy; 711 continue; 712 } 713 714 wp_set_object_terms( $copy_id, $term_ids, $taxonomy ); 715 } 716 717 $result = array( 718 'success' => true, 719 'message' => __( 'Copy created as a draft.', 'mountdev-ai-mcp-connector' ), 720 'source_id' => $post->ID, 721 'post' => $this->format_post( get_post( $copy_id ) ), 722 ); 723 724 if ( ! empty( $skipped_taxonomies ) ) { 725 $result['skipped_taxonomies'] = $skipped_taxonomies; 726 } 727 728 return $result; 575 729 } 576 730 -
mountdev-ai-mcp-connector/trunk/mountdev-ai-mcp-connector.php
r3705477 r3712399 4 4 * Plugin URI: https://cascadiaweb.services/products/mountdev/easy-mcp-connector 5 5 * Description: Transform your WordPress site into an AI-powered Model Context Protocol (MCP) server. Exposes WordPress functionality through a standardized interface for AI agents. 6 * Version: 1. 7.16 * Version: 1.8.0 7 7 * Requires at least: 5.8 8 8 * Requires PHP: 7.4 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'MOUNTDEV_AI_MCP_CONNECTOR_VERSION', '1. 7.1' );22 define( 'MOUNTDEV_AI_MCP_CONNECTOR_VERSION', '1.8.0' ); 23 23 define( 'MOUNTDEV_AI_MCP_CONNECTOR_PLUGIN_FILE', __FILE__ ); 24 24 define( 'MOUNTDEV_AI_MCP_CONNECTOR_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
mountdev-ai-mcp-connector/trunk/readme.txt
r3705489 r3712399 5 5 Tested up to: 7.1.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 7.17 Stable tag: 1.8.0 8 8 License: GPLv3 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.en.html 10 10 11 Connect ChatGPT or Claude to WordPress with one address, no API keys. Your assistant can write posts, update products and fix SEO, within limits you set.11 Connect ChatGPT or Claude to WordPress with one address, no API keys. Your assistant can write posts, update products and fix SEO, within your limits. 12 12 13 13 == Description == … … 39 39 * Any client that speaks the Model Context Protocol 40 40 41 = 38 8tools, across the plugins you already run =42 43 8 1WordPress core, 78 Rank Math SEO, 74 WooCommerce, 71 Elementor (36 free and 35 Pro), 35 Jetpack, 29 Yoast SEO and 20 Contact Form 7. Tools for a plugin appear only when that plugin is active, so the list stays as short as your site is.41 = 389 tools, across the plugins you already run = 42 43 82 WordPress core, 78 Rank Math SEO, 74 WooCommerce, 71 Elementor (36 free and 35 Pro), 35 Jetpack, 29 Yoast SEO and 20 Contact Form 7. Tools for a plugin appear only when that plugin is active, so the list stays as short as your site is. 44 44 45 45 Written for the specific plugin, not guessed at. Rank Math's focus keywords, Elementor's page structure and WooCommerce's variations each store data their own way, and each tool is built for how that plugin actually works. … … 130 130 131 131 == Changelog == 132 133 = 1.8.0 = 134 135 * Added: duplicate_post copies a post, page or other public post type as a new draft. The copy keeps the content, the layout as your builder stored it, the featured image, the page template and the terms, and is titled "(copy)" unless you give it a title. It is always a draft, and it is refused unless you may edit what you are copying 136 * Fixed: Three links in the "Who builds this" box on the settings page (WordPress care, Hosting and Automation) led to pages that no longer exist. They now open the current pages 137 * Fixed: bridge/README.md is included in the plugin again. 1.7.1 left it out by mistake 138 * Changed: The short description is now within WordPress.org's 150 character limit, so the directory no longer cuts it off 132 139 133 140 = 1.7.1 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)