From 0f7d46f9ffed77eef8e782bdb87d48857e6ba07d Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sat, 16 Dec 2023 16:58:44 +0000
Subject: [PATCH] Issue #3397575 by kim.pepper, sourabhjain, alexpott,
 longwave: Deprecate file_progress_implementation() and replace with
 extension_loaded('uploadprogress')

---
 core/modules/file/file.install                | 76 ++++++++----------
 core/modules/file/file.module                 |  6 ++
 .../Controller/FileWidgetAjaxController.php   |  3 +-
 core/modules/file/src/Element/ManagedFile.php | 20 +++--
 .../Plugin/Field/FieldWidget/FileWidget.php   |  2 +-
 .../tests/src/Kernel/LegacyFileModuleTest.php | 30 +++++++
 .../tests/src/Kernel/RequirementsTest.php     | 80 +++++++++++++++++++
 core/phpstan-baseline.neon                    |  5 --
 8 files changed, 160 insertions(+), 62 deletions(-)
 create mode 100644 core/modules/file/tests/src/Kernel/LegacyFileModuleTest.php
 create mode 100644 core/modules/file/tests/src/Kernel/RequirementsTest.php

diff --git a/core/modules/file/file.install b/core/modules/file/file.install
index de86c7bca84b..7745a54041fc 100644
--- a/core/modules/file/file.install
+++ b/core/modules/file/file.install
@@ -65,53 +65,43 @@ function file_schema() {
 function file_requirements($phase) {
   $requirements = [];
 
-  // Check the server's ability to indicate upload progress.
-  if ($phase == 'runtime') {
-    $description = NULL;
-    $implementation = file_progress_implementation();
-    // The environment variable might be missing. Fallback to an empty string to
-    // prevent passing NULL to preg_match(), a few lines later.
-    $server_software = \Drupal::request()->server->get('SERVER_SOFTWARE', '');
+  if ($phase != 'runtime') {
+    return $requirements;
+  }
+
+  $server_software = \Drupal::request()->server->get('SERVER_SOFTWARE', '');
 
-    // Test the web server identity.
-    if (preg_match("/Nginx/i", $server_software)) {
-      $is_nginx = TRUE;
-      $is_apache = FALSE;
-      $fastcgi = FALSE;
-    }
-    elseif (preg_match("/Apache/i", $server_software)) {
-      $is_nginx = FALSE;
-      $is_apache = TRUE;
-      $fastcgi = str_contains($server_software, 'mod_fastcgi') || str_contains($server_software, 'mod_fcgi');
-    }
-    else {
-      $is_nginx = FALSE;
-      $is_apache = FALSE;
-      $fastcgi = FALSE;
-    }
+  // Get the web server identity.
+  $is_nginx = preg_match("/Nginx/i", $server_software);
+  $is_apache = preg_match("/Apache/i", $server_software);
+  $fastcgi = $is_apache && ((str_contains($server_software, 'mod_fastcgi') || str_contains($server_software, 'mod_fcgi')));
+
+  // Check the uploadprogress extension is loaded.
+  if (extension_loaded('uploadprogress')) {
+    $value = t('Enabled (<a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>)');
+    $description = NULL;
+  }
+  else {
+    $value = t('Not enabled');
+    $description = t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.');
+  }
 
-    if (!$is_apache && !$is_nginx) {
-      $value = t('Not enabled');
-      $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.');
-    }
-    elseif ($fastcgi) {
-      $value = t('Not enabled');
-      $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.');
-    }
-    elseif (!$implementation) {
-      $value = t('Not enabled');
-      $description = t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.');
-    }
-    elseif ($implementation == 'uploadprogress') {
-      $value = t('Enabled (<a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>)');
-    }
-    $requirements['file_progress'] = [
-      'title' => t('Upload progress'),
-      'value' => $value,
-      'description' => $description,
-    ];
+  // Adjust the requirement depending on what the server supports.
+  if (!$is_apache && !$is_nginx) {
+    $value = t('Not enabled');
+    $description = t('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.');
+  }
+  elseif ($fastcgi) {
+    $value = t('Not enabled');
+    $description = t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.');
   }
 
+  $requirements['file_progress'] = [
+    'title' => t('Upload progress'),
+    'value' => $value,
+    'description' => $description,
+  ];
+
   return $requirements;
 }
 
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 605add68df73..997879636e87 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -723,8 +723,14 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
  * @return string|false
  *   A string indicating which upload progress system is available. Either "apc"
  *   or "uploadprogress". If neither are available, returns FALSE.
+ *
+ * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
+ *    extension_loaded('uploadprogress') instead.
+ *
+ * @see https://www.drupal.org/node/3397577
  */
 function file_progress_implementation() {
+  @trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use extension_loaded(\'uploadprogress\') instead. See https://www.drupal.org/node/3397577', E_USER_DEPRECATED);
   static $implementation;
   if (!isset($implementation)) {
     $implementation = FALSE;
diff --git a/core/modules/file/src/Controller/FileWidgetAjaxController.php b/core/modules/file/src/Controller/FileWidgetAjaxController.php
index 098337887a94..f27836c2154a 100644
--- a/core/modules/file/src/Controller/FileWidgetAjaxController.php
+++ b/core/modules/file/src/Controller/FileWidgetAjaxController.php
@@ -27,8 +27,7 @@ public function progress($key) {
       'percentage' => -1,
     ];
 
-    $implementation = file_progress_implementation();
-    if ($implementation == 'uploadprogress') {
+    if (extension_loaded('uploadprogress')) {
       $status = uploadprogress_get_info($key);
       if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
         $progress['message'] = t('Uploading... (@current of @total)', [
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index 8056c16da9e4..a27740b6b86e 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -276,19 +276,17 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
     ];
 
     // Add progress bar support to the upload if possible.
-    if ($element['#progress_indicator'] == 'bar' && $implementation = file_progress_implementation()) {
+    if ($element['#progress_indicator'] == 'bar' && extension_loaded('uploadprogress')) {
       $upload_progress_key = mt_rand();
 
-      if ($implementation == 'uploadprogress') {
-        $element['UPLOAD_IDENTIFIER'] = [
-          '#type' => 'hidden',
-          '#value' => $upload_progress_key,
-          '#attributes' => ['class' => ['file-progress']],
-          // Uploadprogress extension requires this field to be at the top of
-          // the form.
-          '#weight' => -20,
-        ];
-      }
+      $element['UPLOAD_IDENTIFIER'] = [
+        '#type' => 'hidden',
+        '#value' => $upload_progress_key,
+        '#attributes' => ['class' => ['file-progress']],
+        // Uploadprogress extension requires this field to be at the top of
+        // the form.
+        '#weight' => -20,
+      ];
 
       // Add the upload progress callback.
       $element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress', ['key' => $upload_progress_key]);
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index c97a4d20b389..458d4234bc5b 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -72,7 +72,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
       '#default_value' => $this->getSetting('progress_indicator'),
       '#description' => $this->t('The throbber display does not show the status of uploads but takes up less space. The progress bar is helpful for monitoring progress on large uploads.'),
       '#weight' => 16,
-      '#access' => file_progress_implementation(),
+      '#access' => extension_loaded('uploadprogress'),
     ];
     return $element;
   }
diff --git a/core/modules/file/tests/src/Kernel/LegacyFileModuleTest.php b/core/modules/file/tests/src/Kernel/LegacyFileModuleTest.php
new file mode 100644
index 000000000000..88eb01bdc675
--- /dev/null
+++ b/core/modules/file/tests/src/Kernel/LegacyFileModuleTest.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\file\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests file module deprecations.
+ *
+ * @group legacy
+ * @group file
+ */
+class LegacyFileModuleTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['file'];
+
+  /**
+   * @covers ::file_progress_implementation
+   */
+  public function testFileProgressDeprecation() {
+    $this->expectDeprecation('file_progress_implementation() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use extension_loaded(\'uploadprogress\') instead. See https://www.drupal.org/node/3397577');
+    $this->assertFalse(\file_progress_implementation());
+  }
+
+}
diff --git a/core/modules/file/tests/src/Kernel/RequirementsTest.php b/core/modules/file/tests/src/Kernel/RequirementsTest.php
new file mode 100644
index 000000000000..4d472644dc4b
--- /dev/null
+++ b/core/modules/file/tests/src/Kernel/RequirementsTest.php
@@ -0,0 +1,80 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\file\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Tests the file requirements.
+ *
+ * @group file
+ */
+class RequirementsTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['file'];
+
+  /**
+   * Tests the file upload requirements.
+   */
+  public function testUploadRequirements(): void {
+    if (\extension_loaded('uploadprogress')) {
+      $this->markTestSkipped('We are testing only when the uploadprogress extension is not loaded.');
+    }
+
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler */
+    $moduleHandler = $this->container->get('module_handler');
+    $moduleHandler->loadInclude('file', 'install');
+
+    // Test unspecified server software.
+    $this->setServerSoftware(NULL);
+    $requirements = \file_requirements('runtime');
+    $this->assertNotEmpty($requirements);
+
+    $this->assertEquals('Upload progress', (string) $requirements['file_progress']['title']);
+    $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']);
+    $this->assertEquals('Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php or Nginx with PHP-FPM.', (string) $requirements['file_progress']['description']);
+
+    // Test Apache + mod_php.
+    $this->setServerSoftware('Apache mod_php');
+    $requirements = \file_requirements('runtime');
+    $this->assertNotEmpty($requirements);
+    $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']);
+    $this->assertEquals('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.', (string) $requirements['file_progress']['description']);
+
+    // Test Apache + mod_fastcgi.
+    $this->setServerSoftware('Apache mod_fastcgi');
+    $requirements = \file_requirements('runtime');
+    $this->assertNotEmpty($requirements);
+    $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']);
+    $this->assertEquals('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php or PHP-FPM and not as FastCGI.', (string) $requirements['file_progress']['description']);
+
+    // Test Nginx.
+    $this->setServerSoftware('Nginx');
+    $requirements = \file_requirements('runtime');
+    $this->assertNotEmpty($requirements);
+    $this->assertEquals('Not enabled', (string) $requirements['file_progress']['value']);
+    $this->assertEquals('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a>.', (string) $requirements['file_progress']['description']);
+
+  }
+
+  /**
+   * Sets the server software attribute in the request.
+   */
+  private function setServerSoftware(?string $software): void {
+    $request = new Request();
+    if (is_string($software)) {
+      $request->server->set('SERVER_SOFTWARE', $software);
+    }
+    $requestStack = new RequestStack();
+    $requestStack->push($request);
+    $this->container->set('request_stack', $requestStack);
+  }
+
+}
diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon
index a37bb883d881..5079add152b2 100644
--- a/core/phpstan-baseline.neon
+++ b/core/phpstan-baseline.neon
@@ -1446,11 +1446,6 @@ parameters:
 			count: 1
 			path: modules/field_ui/src/Form/FieldStorageConfigEditForm.php
 
-		-
-			message: "#^Variable \\$value might not be defined\\.$#"
-			count: 1
-			path: modules/file/file.install
-
 		-
 			message: "#^Variable \\$file_upload in empty\\(\\) always exists and is not falsy\\.$#"
 			count: 1
-- 
GitLab