diff --git a/lib/adminlib.php b/lib/adminlib.php
index 3df2cc3d6ad..7540be3f73c 100644
--- a/lib/adminlib.php
+++ b/lib/adminlib.php
@@ -2543,9 +2543,10 @@ class admin_setting_configexecutable extends admin_setting_configfile {
public function output_html($data, $query='') {
global $CFG;
$default = $this->get_defaultsetting();
+ require_once("$CFG->libdir/filelib.php");
if ($data) {
- if (file_exists($data) and !is_dir($data) and is_executable($data)) {
+ if (file_exists($data) and !is_dir($data) and file_is_executable($data)) {
$executable = '✔';
} else {
$executable = '✘';
diff --git a/lib/filelib.php b/lib/filelib.php
index 246aaad3cb1..c26865d54c8 100644
--- a/lib/filelib.php
+++ b/lib/filelib.php
@@ -2659,6 +2659,35 @@ function file_modify_html_header($text) {
return $text;
}
+/**
+ * Tells whether the filename is executable.
+ *
+ * @link http://php.net/manual/en/function.is-executable.php
+ * @link https://bugs.php.net/bug.php?id=41062
+ * @param string $filename Path to the file.
+ * @return bool True if the filename exists and is executable; otherwise, false.
+ */
+function file_is_executable($filename) {
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ if (is_executable($filename)) {
+ return true;
+ } else {
+ $fileext = strrchr($filename, '.');
+ // If we have an extension we can check if it is listed as executable.
+ if ($fileext && file_exists($filename) && !is_dir($filename)) {
+ $winpathext = strtolower(getenv('PATHEXT'));
+ $winpathexts = explode(';', $winpathext);
+
+ return in_array(strtolower($fileext), $winpathexts);
+ }
+
+ return false;
+ }
+ } else {
+ return is_executable($filename);
+ }
+}
+
/**
* RESTful cURL class
*
diff --git a/lib/filestorage/file_storage.php b/lib/filestorage/file_storage.php
index 6ccb84e93d7..e4b1dde181f 100644
--- a/lib/filestorage/file_storage.php
+++ b/lib/filestorage/file_storage.php
@@ -224,7 +224,7 @@ class file_storage {
protected function create_converted_document(stored_file $file, $format) {
global $CFG;
- if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
+ if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return false;
}
diff --git a/lib/tests/unoconv_test.php b/lib/tests/unoconv_test.php
index 3038abb3a5b..19817df320a 100644
--- a/lib/tests/unoconv_test.php
+++ b/lib/tests/unoconv_test.php
@@ -71,7 +71,7 @@ class core_unoconv_testcase extends advanced_testcase {
public function test_generate_pdf() {
global $CFG;
- if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
+ if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return $this->markTestSkipped();
}
@@ -90,7 +90,7 @@ class core_unoconv_testcase extends advanced_testcase {
public function test_generate_markdown() {
global $CFG;
- if (empty($CFG->pathtounoconv) || !is_executable(trim($CFG->pathtounoconv))) {
+ if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return $this->markTestSkipped();
}
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index 172d9c2eab7..78cd9490d17 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -115,7 +115,8 @@ information provided here is intended especially for developers.
actual URLs in $text to encoded URLs in the @@PLUGINFILE@@ form.
* behat_util::is_server_running() is removed, please use behat_util::check_server_status() instead.
* Behat\Mink\Selector\SelectorsHandler::xpathLiteral() method is deprecated use behat_context_helper::escape instead
- when building Xpath, or pass the unescaped value when using the named selector.',
+ when building Xpath, or pass the unescaped value when using the named selector.
+* Add new file_is_executable(), to consistently check for executables even in Windows (PHP bug #41062).
=== 3.0 ===