From 24f847661fc0464a785add11b9320d16dea22235 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 16 May 2009 02:04:36 +0000 Subject: [PATCH] Support IIS 7.0 URL Rewrite Module. Props ruslany. Hat tips to peaceablewhale, hakre, Denis-de-Bernardy, sivel. fixes #8974 git-svn-id: http://svn.automattic.com/wordpress/trunk@11350 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/includes/misc.php | 241 ++++++++++++++++++++++++++++++++- wp-admin/options-permalink.php | 57 ++++++-- wp-includes/rewrite.php | 32 +++++ wp-includes/vars.php | 7 + 4 files changed, 322 insertions(+), 15 deletions(-) diff --git a/wp-admin/includes/misc.php b/wp-admin/includes/misc.php index 80008cbcad..ace5d3ff0c 100644 --- a/wp-admin/includes/misc.php +++ b/wp-admin/includes/misc.php @@ -135,6 +135,34 @@ function save_mod_rewrite_rules() { return false; } +/** + * Updates the IIS web.config file with the current rules if it is writable. + * If the permalinks do not require rewrite rules then the rules are deleted from the web.config file. + * + * @since 2.8.0 + * + * @return bool True if web.config was updated successfully + */ +function iis7_save_url_rewrite_rules(){ + global $wp_rewrite; + + $home_path = get_home_path(); + $web_config_file = $home_path . 'web.config'; + + // Using win_is_writable() instead of is_writable() because of a bug in Windows PHP + if ( ( ! file_exists($web_config_file) && win_is_writable($home_path) && $wp_rewrite->using_mod_rewrite_permalinks() ) || win_is_writable($web_config_file) ) { + if ( iis7_supports_permalinks() ) { + $rule = $wp_rewrite->iis7_url_rewrite_rules(); + if ( ! empty($rule) ) { + return iis7_add_rewrite_rule($web_config_file, $rule); + } else { + return iis7_delete_rewrite_rule($web_config_file); + } + } + } + return false; +} + /** * {@internal Missing Short Description}} * @@ -370,4 +398,215 @@ function wp_menu_unfold() { exit; } } -?> \ No newline at end of file + +/** + * Check if IIS 7 supports pretty permalinks + * + * @since 2.8.0 + * + * @return bool + */ +function iis7_supports_permalinks() { + global $is_iis7; + + $supports_permalinks = false; + if ( $is_iis7 ) { + /* First we check if the DOMDocument class exists. If it does not exist, + * which is the case for PHP 4.X, then we cannot easily update the xml configuration file, + * hence we just bail out and tell user that pretty permalinks cannot be used. + * This is not a big issue because PHP 4.X is going to be depricated and for IIS it + * is recommended to use PHP 5.X NTS. + * Next we check if the URL Rewrite Module 1.1 is loaded and enabled for the web site. When + * URL Rewrite 1.1 is loaded it always sets a server variable called 'IIS_UrlRewriteModule'. + * Lastly we make sure that PHP is running via FastCGI. This is important because if it runs + * via ISAPI then pretty permalinks will not work. + */ + $supports_permalinks = class_exists('DOMDocument') && isset($_SERVER['IIS_UrlRewriteModule']) && ( php_sapi_name() == 'cgi-fcgi' ); + } + + return apply_filters('iis7_supports_permalinks', $supports_permalinks); +} + +/** + * Check if rewrite rule for WordPress already exists in the IIS 7 configuration file + * + * @since 2.8.0 + * + * @return bool + * @param string $filename The file path to the configuration file + */ +function iis7_rewrite_rule_exists($filename) { + if ( ! file_exists($filename) ) + return false; + if ( ! class_exists('DOMDocument') ) + return false; + + $doc = new DOMDocument(); + if ( $doc->load($filename) === false ) + return false; + $xpath = new DOMXPath($doc); + $rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[@name=\'wordpress\']'); + if ( $rules->length == 0 ) + return false; + else + return true; +} + +/** + * Delete WordPress rewrite rule from web.config file if it exists there + * + * @since 2.8.0 + * + * @param string $filename Name of the configuration file + * @return bool + */ +function iis7_delete_rewrite_rule($filename) { + // If configuration file does not exist then rules also do not exist so there is nothing to delete + if ( ! file_exists($filename) ) + return true; + + if ( ! class_exists('DOMDocument') ) + return false; + + $doc = new DOMDocument(); + $doc->preserveWhiteSpace = false; + + if ( $doc -> load($filename) === false ) + return false; + $xpath = new DOMXPath($doc); + $rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[@name=\'wordpress\']'); + if ( $rules->length > 0 ) { + $child = $rules->item(0); + $parent = $child->parentNode; + $parent->removeChild($child); + $doc->formatOutput = true; + saveDomDocument($doc, $filename); + } + return true; +} + +/** + * Add WordPress rewrite rule to the IIS 7 configuration file. + * + * @since 2.8.0 + * + * @param string $filename The file path to the configuration file + * @param string $rewrite_rule The XML fragment with URL Rewrite rule + * @return bool + */ +function iis7_add_rewrite_rule($filename, $rewrite_rule) { + if ( ! class_exists('DOMDocument') ) + return false; + + // If configuration file does not exist then we create one. + if ( ! file_exists($filename) ) { + $fp = fopen( $filename, 'w'); + fwrite($fp, ''); + fclose($fp); + } + + $doc = new DOMDocument(); + $doc->preserveWhiteSpace = false; + + if ( $doc->load($filename) === false ) + return false; + + $xpath = new DOMXPath($doc); + + // First check if the rule already exists as in that case there is no need to re-add it + $wordpress_rules = $xpath->query('/configuration/system.webServer/rewrite/rules/rule[@name=\'wordpress\']'); + if ( $wordpress_rules->length > 0 ) + return true; + + // Check the XPath to the rewrite rule and create XML nodes if they do not exist + $xmlnodes = $xpath->query('/configuration/system.webServer/rewrite/rules'); + if ( $xmlnodes->length > 0 ) { + $rules_node = $xmlnodes->item(0); + } else { + $rules_node = $doc->createElement('rules'); + + $xmlnodes = $xpath->query('/configuration/system.webServer/rewrite'); + if ( $xmlnodes->length > 0 ) { + $rewrite_node = $xmlnodes->item(0); + $rewrite_node->appendChild($rules_node); + } else { + $rewrite_node = $doc->createElement('rewrite'); + $rewrite_node->appendChild($rules_node); + + $xmlnodes = $xpath->query('/configuration/system.webServer'); + if ( $xmlnodes->length > 0 ) { + $system_webServer_node = $xmlnodes->item(0); + $system_webServer_node->appendChild($rewrite_node); + } else { + $system_webServer_node = $doc->createElement('system.webServer'); + $system_webServer_node->appendChild($rewrite_node); + + $xmlnodes = $xpath->query('/configuration'); + if ( $xmlnodes->length > 0 ) { + $config_node = $xmlnodes->item(0); + $config_node->appendChild($system_webServer_node); + } else { + $config_node = $doc->createElement('configuration'); + $doc->appendChild($config_node); + $config_node->appendChild($system_webServer_node); + } + } + } + } + + $rule_fragment = $doc->createDocumentFragment(); + $rule_fragment->appendXML($rewrite_rule); + $rules_node->appendChild($rule_fragment); + + $doc->formatOutput = true; + saveDomDocument($doc, $filename); + + return true; +} + +/** + * Saves the XML document into a file + * + * @since 2.8.0 + * + * @param DOMDocument $doc + * @param string $filename + */ +function saveDomDocument($doc, $filename) { + $config = $doc->saveXML(); + $config = preg_replace("/([^\r])\n/", "$1\r\n", $config); + $fp = fopen($filename, 'w'); + fwrite($fp, $config); + fclose($fp); +} + +/** + * Workaround for Windows bug in is_writable() function + * + * @since 2.8.0 + * + * @param object $path + * @return bool + */ +function win_is_writable($path) { + /* will work in despite of Windows ACLs bug + * NOTE: use a trailing slash for folders!!! + * see http://bugs.php.net/bug.php?id=27609 + * see http://bugs.php.net/bug.php?id=30931 + */ + + if ( $path{strlen($path)-1} == '/' ) // recursively return a temporary file path + return win_is_writable($path . uniqid(mt_rand()) . '.tmp'); + else if ( is_dir($path) ) + return win_is_writable($path . '/' . uniqid(mt_rand()) . '.tmp'); + // check tmp file for read/write capabilities + $rm = file_exists($path); + $f = @fopen($path, 'a'); + if ($f===false) + return false; + fclose($f); + if ( ! $rm ) + unlink($path); + return true; +} +?> diff --git a/wp-admin/options-permalink.php b/wp-admin/options-permalink.php index 0feb5e41cf..124abd822e 100644 --- a/wp-admin/options-permalink.php +++ b/wp-admin/options-permalink.php @@ -70,6 +70,7 @@ add_filter('admin_head', 'add_js'); include('admin-header.php'); $home_path = get_home_path(); +$iis7_permalinks = iis7_supports_permalinks(); if ( isset($_POST['permalink_structure']) || isset($_POST['category_base']) ) { check_admin_referer('update-permalink'); @@ -100,12 +101,19 @@ $permalink_structure = get_option('permalink_structure'); $category_base = get_option('category_base'); $tag_base = get_option( 'tag_base' ); -if ( (!file_exists($home_path.'.htaccess') && is_writable($home_path)) || is_writable($home_path.'.htaccess') ) - $writable = true; -else - $writable = false; +if ( $iis7_permalinks ) { + if ( ( ! file_exists($home_path . 'web.config') && win_is_writable($home_path) ) || win_is_writable($home_path . 'web.config') ) + $writable = true; + else + $writable = false; +} else { + if ( ( ! file_exists($home_path . '.htaccess') && is_writable($home_path) ) || is_writable($home_path . '.htaccess') ) + $writable = true; + else + $writable = false; +} -if ($wp_rewrite->using_index_permalinks()) +if ( $wp_rewrite->using_index_permalinks() ) $usingpi = true; else $usingpi = false; @@ -115,11 +123,21 @@ $wp_rewrite->flush_rules();

+if ( $iis7_permalinks ) { + if ( $permalink_structure && ! $usingpi && ! $writable ) + _e('You should update your web.config now'); + else if ( $permalink_structure && ! $usingpi && $writable) + _e('Permalink structure updated. Remove write access on web.config file now!'); + else + _e('Permalink structure updated'); +} else { + if ( $permalink_structure && ! $usingpi && ! $writable ) + _e('You should update your .htaccess now.'); + else + _e('Permalink structure updated.'); +} +?> +

@@ -134,7 +152,7 @@ else

- +

URLs here. For example, using topics as your category base would make your category links like http://example.org/topics/uncategorized/. If you leave these blank the defaults will be used.') ?>

URLs here. For example, using topics as your category base would make your category links like http://example.org/index.php/topics/uncategorized/. If you leave these blank the defaults will be used.') ?>

@@ -203,12 +221,23 @@ $structures = array(

- -

.htaccess file were writable, we could do this automatically, but it isn’t so these are the mod_rewrite rules you should have in your .htaccess file. Click in the field and press CTRL + a to select all.') ?>

+ +

web.config file were writable, we could do this automatically, but it isn’t so this is the url rewrite rule you should have in your web.config file. Click in the field and press CTRL + a to select all. Then insert this rule inside of the /<configuration>/<system.webServer>/<rewrite>/<rules> element in web.config file.') ?>

+
+ +

+
+

web.config file writable for us to generate rewrite rules automatically, do not forget to revert the permissions after rule has been saved.') ?>

+ + +

.htaccess file were writable, we could do this automatically, but it isn’t so these are the mod_rewrite rules you should have in your .htaccess file. Click in the field and press CTRL + a to select all.') ?>

+
diff --git a/wp-includes/rewrite.php b/wp-includes/rewrite.php index f1b4b79d0e..f0b045475a 100644 --- a/wp-includes/rewrite.php +++ b/wp-includes/rewrite.php @@ -1695,6 +1695,36 @@ class WP_Rewrite { return $rules; } + + /** + * Retrieve IIS7 URL Rewrite formatted rewrite rules to write to web.config file. + * + * Does not actually write to the web.config file, but creates the rules for + * the process that will. + * + * @since 2.8.0 + * @access public + * + * @return string + */ + function iis7_url_rewrite_rules(){ + + if ( ! $this->using_permalinks()) { + return ''; + } + $rules = "\n"; + $rules .= " \n"; + $rules .= " \n"; + $rules .= " \n"; + $rules .= " \n"; + $rules .= " \n"; + $rules .= " \n"; + $rules .= ""; + + $rules = apply_filters('iis7_url_rewrite_rules', $rules); + + return $rules; + } /** * Add a straight rewrite rule. @@ -1790,6 +1820,8 @@ class WP_Rewrite { $this->wp_rewrite_rules(); if ( function_exists('save_mod_rewrite_rules') ) save_mod_rewrite_rules(); + if ( function_exists('iis7_save_url_rewrite_rules') ) + iis7_save_url_rewrite_rules(); } /** diff --git a/wp-includes/vars.php b/wp-includes/vars.php index 6edc5440be..4153658764 100644 --- a/wp-includes/vars.php +++ b/wp-includes/vars.php @@ -73,4 +73,11 @@ $is_apache = ((strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false) || (strp */ $is_IIS = (strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false) ? true : false; +/** + * Whether the server software is IIS 7.X + * @global bool $is_IIS7 + */ +$is_iis7 = ($is_IIS && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7.') !== false) ? true : false; + + ?> \ No newline at end of file