github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/drupal.go (about)

     1  package ddevapp
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"text/template"
     9  
    10  	"github.com/ddev/ddev/pkg/archive"
    11  	"github.com/ddev/ddev/pkg/dockerutil"
    12  	"github.com/ddev/ddev/pkg/fileutil"
    13  	"github.com/ddev/ddev/pkg/nodeps"
    14  	"github.com/ddev/ddev/pkg/output"
    15  	"github.com/ddev/ddev/pkg/util"
    16  )
    17  
    18  // DrupalSettings encapsulates all the configurations for a Drupal site.
    19  type DrupalSettings struct {
    20  	DeployName       string
    21  	DeployURL        string
    22  	DatabaseName     string
    23  	DatabaseUsername string
    24  	DatabasePassword string
    25  	DatabaseHost     string
    26  	DatabaseDriver   string
    27  	DatabasePort     string
    28  	HashSalt         string
    29  	Signature        string
    30  	SitePath         string
    31  	SiteSettings     string
    32  	SiteSettingsDdev string
    33  	SyncDir          string
    34  	DockerIP         string
    35  	DBPublishedPort  int
    36  }
    37  
    38  // NewDrupalSettings produces a DrupalSettings object with default.
    39  func NewDrupalSettings(app *DdevApp) *DrupalSettings {
    40  	dockerIP, _ := dockerutil.GetDockerIP()
    41  	dbPublishedPort, _ := app.GetPublishedPort("db")
    42  
    43  	settings := &DrupalSettings{
    44  		DatabaseName:     "db",
    45  		DatabaseUsername: "db",
    46  		DatabasePassword: "db",
    47  		DatabaseHost:     "db",
    48  		DatabaseDriver:   "mysql",
    49  		DatabasePort:     GetExposedPort(app, "db"),
    50  		HashSalt:         util.HashSalt(app.Name),
    51  		Signature:        nodeps.DdevFileSignature,
    52  		SitePath:         path.Join("sites", "default"),
    53  		SiteSettings:     "settings.php",
    54  		SiteSettingsDdev: "settings.ddev.php",
    55  		SyncDir:          path.Join("files", "sync"),
    56  		DockerIP:         dockerIP,
    57  		DBPublishedPort:  dbPublishedPort,
    58  	}
    59  	if app.Type == "drupal6" {
    60  		settings.DatabaseDriver = "mysqli"
    61  	}
    62  	if app.Database.Type == nodeps.Postgres {
    63  		settings.DatabaseDriver = "pgsql"
    64  	}
    65  	return settings
    66  }
    67  
    68  // settingsIncludeStanza defines the template that will be appended to
    69  // a project's settings.php in the event that the file already exists.
    70  const settingsIncludeStanza = `
    71  // Automatically generated include for settings managed by ddev.
    72  $ddev_settings = dirname(__FILE__) . '/settings.ddev.php';
    73  if (getenv('IS_DDEV_PROJECT') == 'true' && is_readable($ddev_settings)) {
    74    require $ddev_settings;
    75  }
    76  `
    77  
    78  // manageDrupalSettingsFile will direct inspecting and writing of settings.php.
    79  func manageDrupalSettingsFile(app *DdevApp, drupalConfig *DrupalSettings, appType string) error {
    80  	// We'll be writing/appending to the settings files and parent directory, make sure we have permissions to do so
    81  	if err := drupalEnsureWritePerms(app); err != nil {
    82  		return err
    83  	}
    84  
    85  	if !fileutil.FileExists(app.SiteSettingsPath) {
    86  		output.UserOut.Printf("No %s file exists, creating one", drupalConfig.SiteSettings)
    87  
    88  		if err := writeDrupalSettingsPHP(app.SiteSettingsPath, appType); err != nil {
    89  			return fmt.Errorf("failed to write: %v", err)
    90  		}
    91  	}
    92  
    93  	included, err := settingsHasInclude(drupalConfig, app.SiteSettingsPath)
    94  	if err != nil {
    95  		return fmt.Errorf("failed to check for include: %v", err)
    96  	}
    97  
    98  	if included {
    99  		util.Debug("Existing %s file includes %s", drupalConfig.SiteSettings, drupalConfig.SiteSettingsDdev)
   100  	} else {
   101  		util.Debug("Existing %s file does not include %s, modifying to include DDEV settings", drupalConfig.SiteSettings, drupalConfig.SiteSettingsDdev)
   102  
   103  		if err := appendIncludeToDrupalSettingsFile(app.SiteSettingsPath, app.Type); err != nil {
   104  			return fmt.Errorf("failed to include %s in %s: %v", drupalConfig.SiteSettingsDdev, drupalConfig.SiteSettings, err)
   105  		}
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // writeDrupalSettingsPHP creates the project's settings.php if it doesn't exist
   112  func writeDrupalSettingsPHP(filePath string, appType string) error {
   113  	content, err := bundledAssets.ReadFile(path.Join("drupal", appType, "settings.php"))
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	// Ensure target directory exists and is writable
   119  	dir := filepath.Dir(filePath)
   120  	if err = os.Chmod(dir, 0755); os.IsNotExist(err) {
   121  		if err = os.MkdirAll(dir, 0755); err != nil {
   122  			return err
   123  		}
   124  	} else if err != nil {
   125  		return err
   126  	}
   127  
   128  	// Create file
   129  	err = os.WriteFile(filePath, content, 0755)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  // createDrupalSettingsPHP manages creation and modification of settings.php and settings.ddev.php.
   138  // If a settings.php file already exists, it will be modified to ensure that it includes
   139  // settings.ddev.php, which contains ddev-specific configuration.
   140  func createDrupalSettingsPHP(app *DdevApp) (string, error) {
   141  	// Currently there isn't any customization done for the drupal config, but
   142  	// we may want to do some kind of customization in the future.
   143  	drupalConfig := NewDrupalSettings(app)
   144  
   145  	if err := manageDrupalSettingsFile(app, drupalConfig, app.Type); err != nil {
   146  		return "", err
   147  	}
   148  
   149  	if err := writeDrupalSettingsDdevPhp(drupalConfig, app.SiteDdevSettingsFile, app); err != nil {
   150  		return "", fmt.Errorf("`failed to write` Drupal settings file %s: %v", app.SiteDdevSettingsFile, err)
   151  	}
   152  
   153  	return app.SiteDdevSettingsFile, nil
   154  }
   155  
   156  // writeDrupalSettingsDdevPhp dynamically produces valid settings.ddev.php file by combining a configuration
   157  // object with a data-driven template.
   158  func writeDrupalSettingsDdevPhp(settings *DrupalSettings, filePath string, app *DdevApp) error {
   159  	if fileutil.FileExists(filePath) {
   160  		// Check if the file is managed by ddev.
   161  		signatureFound, err := fileutil.FgrepStringInFile(filePath, nodeps.DdevFileSignature)
   162  		if err != nil {
   163  			return err
   164  		}
   165  
   166  		// If the signature wasn't found, warn the user and return.
   167  		if !signatureFound {
   168  			util.Warning("%s already exists and is managed by the user.", filepath.Base(filePath))
   169  			return nil
   170  		}
   171  	}
   172  
   173  	drupalVersion, err := GetDrupalVersion(app)
   174  	if err != nil || drupalVersion == "" {
   175  		// todo: Reconsider this logic for default version
   176  		drupalVersion = "10"
   177  	}
   178  	t, err := template.New("settings.ddev.php").ParseFS(bundledAssets, path.Join("drupal", "drupal"+drupalVersion, "settings.ddev.php"))
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	// Ensure target directory exists and is writable
   184  	dir := filepath.Dir(filePath)
   185  	if err = os.Chmod(dir, 0755); os.IsNotExist(err) {
   186  		if err = os.MkdirAll(dir, 0755); err != nil {
   187  			return err
   188  		}
   189  	} else if err != nil {
   190  		return err
   191  	}
   192  
   193  	file, err := os.Create(filePath)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	err = t.Execute(file, settings)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	util.CheckClose(file)
   202  	return nil
   203  }
   204  
   205  // WriteDrushrc writes out drushrc.php based on passed-in values.
   206  // This works on Drupal 6 and Drupal 7 or with drush8 and older
   207  func WriteDrushrc(app *DdevApp, filePath string) error {
   208  	if fileutil.FileExists(filePath) {
   209  		// Check if the file is managed by ddev.
   210  		signatureFound, err := fileutil.FgrepStringInFile(filePath, nodeps.DdevFileSignature)
   211  		if err != nil {
   212  			return err
   213  		}
   214  
   215  		// If the signature wasn't found, warn the user and return.
   216  		if !signatureFound {
   217  			util.Warning("%s already exists and is managed by the user.", filepath.Base(filePath))
   218  			return nil
   219  		}
   220  	}
   221  
   222  	uri := app.GetPrimaryURL()
   223  	drushContents := []byte(`<?php
   224  
   225  /**
   226   * @file
   227   * ` + nodeps.DdevFileSignature + `: Automatically generated drushrc.php file (for Drush 8)
   228   * DDEV manages this file and may delete or overwrite it unless this comment is removed.
   229   * Remove this comment if you don't want DDEV to manage this file.
   230   */
   231  
   232  if (getenv('IS_DDEV_PROJECT') == 'true') {
   233    $options['l'] = "` + uri + `";
   234  }
   235  `)
   236  
   237  	// Ensure target directory exists and is writable
   238  	dir := filepath.Dir(filePath)
   239  	if err := os.Chmod(dir, 0755); os.IsNotExist(err) {
   240  		if err = os.MkdirAll(dir, 0755); err != nil {
   241  			return err
   242  		}
   243  	} else if err != nil {
   244  		return err
   245  	}
   246  
   247  	err := os.WriteFile(filePath, drushContents, 0666)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  // getDrupalUploadDirs will return the default paths.
   256  func getDrupalUploadDirs(_ *DdevApp) []string {
   257  	uploadDirs := []string{"sites/default/files"}
   258  
   259  	return uploadDirs
   260  }
   261  
   262  // DrupalHooks adds d8+-specific hooks example for post-import-db
   263  const DrupalHooks = `# post-import-db:
   264  #   - exec: drush sql:sanitize
   265  #   - exec: drush updatedb
   266  #   - exec: drush cache:rebuild
   267  `
   268  
   269  // Drupal7Hooks adds a d7-specific hooks example for post-import-db
   270  const Drupal7Hooks = `#  post-import-db:
   271  #    - exec: drush cc all
   272  `
   273  
   274  // getDrupal7Hooks for appending as byte array
   275  func getDrupal7Hooks() []byte {
   276  	return []byte(Drupal7Hooks)
   277  }
   278  
   279  // getDrupal6Hooks for appending as byte array
   280  func getDrupal6Hooks() []byte {
   281  	// We don't have anything new to add yet, so use Drupal7 version
   282  	return []byte(Drupal7Hooks)
   283  }
   284  
   285  // getDrupalHooks for appending as byte array
   286  func getDrupalHooks() []byte {
   287  	return []byte(DrupalHooks)
   288  }
   289  
   290  // setDrupalSiteSettingsPaths sets the paths to settings.php/settings.ddev.php
   291  // for templating.
   292  func setDrupalSiteSettingsPaths(app *DdevApp) {
   293  	drupalConfig := NewDrupalSettings(app)
   294  	settingsFileBasePath := filepath.Join(app.AppRoot, app.Docroot)
   295  	app.SiteSettingsPath = filepath.Join(settingsFileBasePath, drupalConfig.SitePath, drupalConfig.SiteSettings)
   296  	app.SiteDdevSettingsFile = filepath.Join(settingsFileBasePath, drupalConfig.SitePath, drupalConfig.SiteSettingsDdev)
   297  }
   298  
   299  // isDrupal7App returns true if the app is of type drupal7
   300  func isDrupal7App(app *DdevApp) bool {
   301  	if _, err := os.Stat(filepath.Join(app.AppRoot, app.Docroot, "misc/ajax.js")); err == nil {
   302  		return true
   303  	}
   304  	return false
   305  }
   306  
   307  // GetDrupalVersion finds the drupal8+ version so it can be used
   308  // for setting requirements.
   309  // It can only work if there is configured Drupal8+ code
   310  func GetDrupalVersion(app *DdevApp) (string, error) {
   311  	// For drupal6/7 we use the apptype provided as version
   312  	switch app.Type {
   313  	case nodeps.AppTypeDrupal6:
   314  		return "6", nil
   315  	case nodeps.AppTypeDrupal7:
   316  		return "7", nil
   317  	}
   318  	// Otherwise figure out the version from existing code
   319  	f := filepath.Join(app.AppRoot, app.Docroot, "core/lib/Drupal.php")
   320  	hasVersion, matches, err := fileutil.GrepStringInFile(f, `const VERSION = '([0-9]+)`)
   321  	v := ""
   322  	if hasVersion {
   323  		v = matches[1]
   324  	}
   325  	return v, err
   326  }
   327  
   328  // isDrupalApp returns true if the app is drupal
   329  func isDrupalApp(app *DdevApp) bool {
   330  	v, err := GetDrupalVersion(app)
   331  	if err == nil && v != "" {
   332  		return true
   333  	}
   334  	return false
   335  }
   336  
   337  // isDrupal6App returns true if the app is of type Drupal6
   338  func isDrupal6App(app *DdevApp) bool {
   339  	if _, err := os.Stat(filepath.Join(app.AppRoot, app.Docroot, "misc/ahah.js")); err == nil {
   340  		return true
   341  	}
   342  	return false
   343  }
   344  
   345  // drupal6ConfigOverrideAction overrides php_version for D6
   346  func drupal6ConfigOverrideAction(app *DdevApp) error {
   347  	app.PHPVersion = nodeps.PHP56
   348  	return nil
   349  }
   350  
   351  // drupal7ConfigOverrideAction overrides php_version for D7
   352  func drupal7ConfigOverrideAction(app *DdevApp) error {
   353  	app.PHPVersion = nodeps.PHP82
   354  	return nil
   355  }
   356  
   357  // drupalConfigOverrideAction selects proper versions for
   358  func drupalConfigOverrideAction(app *DdevApp) error {
   359  	v, err := GetDrupalVersion(app)
   360  	if err != nil || v == "" {
   361  		util.Warning("Unable to detect Drupal version, continuing")
   362  		return nil
   363  	}
   364  	// If there is no database, update it to the default one,
   365  	// otherwise show a warning to the user.
   366  	if !nodeps.ArrayContainsString(app.GetOmittedContainers(), "db") {
   367  		if dbType, err := app.GetExistingDBType(); err == nil && dbType == "" {
   368  			app.Database = DatabaseDefault
   369  		} else if app.Database != DatabaseDefault && v != "8" {
   370  			defaultType := DatabaseDefault.Type + ":" + DatabaseDefault.Version
   371  			util.Warning("Default database type is %s, but the current actual database type is %s, you may want to migrate with 'ddev debug migrate-database %s'.", defaultType, dbType, defaultType)
   372  		}
   373  	}
   374  	switch v {
   375  	case "8":
   376  		app.PHPVersion = nodeps.PHP74
   377  		app.Database = DatabaseDesc{Type: nodeps.MariaDB, Version: nodeps.MariaDB104}
   378  	case "9":
   379  		app.PHPVersion = nodeps.PHP81
   380  	case "10":
   381  		app.PHPVersion = nodeps.PHP83
   382  	case "11":
   383  		app.PHPVersion = nodeps.PHP83
   384  		app.CorepackEnable = true
   385  	}
   386  	return nil
   387  }
   388  
   389  func drupalPostStartAction(app *DdevApp) error {
   390  	if !nodeps.ArrayContainsString(app.GetOmittedContainers(), "db") && (isDrupalApp(app)) {
   391  		err := app.Wait([]string{nodeps.DBContainer})
   392  		if err != nil {
   393  			return err
   394  		}
   395  		// pg_trm extension is required in Drupal9.5+
   396  		if app.Database.Type == nodeps.Postgres {
   397  			stdout, stderr, err := app.Exec(&ExecOpts{
   398  				Service:   "db",
   399  				Cmd:       `psql -q -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" 2>/dev/null`,
   400  				NoCapture: false,
   401  			})
   402  			if err != nil {
   403  				util.Warning("unable to CREATE EXTENSION pg_trm: stdout='%s', stderr='%s', err=%v", stdout, stderr, err)
   404  			}
   405  		}
   406  		// SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED required in Drupal 9.5+
   407  		if app.Database.Type == nodeps.MariaDB || app.Database.Type == nodeps.MySQL {
   408  			_, _, err := app.Exec(&ExecOpts{
   409  				Service:   "db",
   410  				Cmd:       `mysql -uroot -proot -e "SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;" >/dev/null 2>&1`,
   411  				NoCapture: false,
   412  			})
   413  			if err != nil {
   414  				util.Warning("Unable to SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED: %v", err)
   415  			}
   416  		}
   417  	}
   418  	// Return early because we aren't expected to manage settings.
   419  	if app.DisableSettingsManagement {
   420  		return nil
   421  	}
   422  	if err := createDrupal8SyncDir(app); err != nil {
   423  		return err
   424  	}
   425  
   426  	//nolint: revive
   427  	if err := drupalEnsureWritePerms(app); err != nil {
   428  		return err
   429  	}
   430  	return nil
   431  }
   432  
   433  // drupal7PostStartAction handles default post-start actions for D7 apps, like ensuring
   434  // useful permissions settings on sites/default.
   435  func drupal7PostStartAction(app *DdevApp) error {
   436  	// Return early because we aren't expected to manage settings.
   437  	if app.DisableSettingsManagement {
   438  		return nil
   439  	}
   440  	if err := drupalEnsureWritePerms(app); err != nil {
   441  		return err
   442  	}
   443  
   444  	err := WriteDrushrc(app, filepath.Join(filepath.Dir(app.SiteSettingsPath), "drushrc.php"))
   445  	if err != nil {
   446  		util.Warning("Failed to WriteDrushrc: %v", err)
   447  	}
   448  
   449  	return nil
   450  }
   451  
   452  // drupal6PostStartAction handles default post-start actions for D6 apps, like ensuring
   453  // useful permissions settings on sites/default.
   454  func drupal6PostStartAction(app *DdevApp) error {
   455  	// Return early because we aren't expected to manage settings.
   456  	if app.DisableSettingsManagement {
   457  		return nil
   458  	}
   459  
   460  	if err := drupalEnsureWritePerms(app); err != nil {
   461  		return err
   462  	}
   463  
   464  	err := WriteDrushrc(app, filepath.Join(filepath.Dir(app.SiteSettingsPath), "drushrc.php"))
   465  	if err != nil {
   466  		util.Warning("Failed to WriteDrushrc: %v", err)
   467  	}
   468  	return nil
   469  }
   470  
   471  // drupalEnsureWritePerms will ensure sites/default and sites/default/settings.php will
   472  // have the appropriate permissions for development.
   473  func drupalEnsureWritePerms(app *DdevApp) error {
   474  	util.Debug("Ensuring write permissions for %s", app.GetName())
   475  	var writePerms os.FileMode = 0200
   476  
   477  	settingsDir := path.Dir(app.SiteSettingsPath)
   478  	makeWritable := []string{
   479  		settingsDir,
   480  		app.SiteSettingsPath,
   481  		app.SiteDdevSettingsFile,
   482  		path.Join(settingsDir, "services.yml"),
   483  	}
   484  
   485  	for _, o := range makeWritable {
   486  		stat, err := os.Stat(o)
   487  		if err != nil {
   488  			if !os.IsNotExist(err) {
   489  				util.Warning("Unable to ensure write permissions: %v", err)
   490  			}
   491  
   492  			continue
   493  		}
   494  
   495  		if err := os.Chmod(o, stat.Mode()|writePerms); err != nil {
   496  			// Warn the user, but continue.
   497  			util.Warning("Unable to set permissions: %v", err)
   498  		}
   499  	}
   500  
   501  	return nil
   502  }
   503  
   504  // createDrupal8SyncDir creates a Drupal 8 app's sync directory
   505  func createDrupal8SyncDir(app *DdevApp) error {
   506  	// Currently there isn't any customization done for the drupal config, but
   507  	// we may want to do some kind of customization in the future.
   508  	drupalConfig := NewDrupalSettings(app)
   509  
   510  	syncDirPath := path.Join(app.GetAppRoot(), app.GetDocroot(), "sites/default", drupalConfig.SyncDir)
   511  	if fileutil.FileExists(syncDirPath) {
   512  		return nil
   513  	}
   514  
   515  	if err := os.MkdirAll(syncDirPath, 0755); err != nil {
   516  		return fmt.Errorf("failed to create sync directory (%s): %v", syncDirPath, err)
   517  	}
   518  
   519  	return nil
   520  }
   521  
   522  // settingsHasInclude determines if the settings.php or equivalent includes settings.ddev.php or equivalent.
   523  // This is done by looking for the DDEV settings file (settings.ddev.php) in settings.php.
   524  func settingsHasInclude(drupalConfig *DrupalSettings, siteSettingsPath string) (bool, error) {
   525  	included, err := fileutil.FgrepStringInFile(siteSettingsPath, drupalConfig.SiteSettingsDdev)
   526  	if err != nil {
   527  		return false, err
   528  	}
   529  
   530  	return included, nil
   531  }
   532  
   533  // appendIncludeToDrupalSettingsFile modifies the settings.php file to include the settings.ddev.php
   534  // file, which contains ddev-specific configuration.
   535  func appendIncludeToDrupalSettingsFile(siteSettingsPath string, appType string) error {
   536  	// Check if file is empty
   537  	contents, err := os.ReadFile(siteSettingsPath)
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	// If the file is empty, write the complete settings file and return
   543  	if len(contents) == 0 {
   544  		return writeDrupalSettingsPHP(siteSettingsPath, appType)
   545  	}
   546  
   547  	// The file is not empty, open it for appending
   548  	file, err := os.OpenFile(siteSettingsPath, os.O_RDWR|os.O_APPEND, 0644)
   549  	if err != nil {
   550  		return err
   551  	}
   552  	defer util.CheckClose(file)
   553  
   554  	_, err = file.Write([]byte(settingsIncludeStanza))
   555  	if err != nil {
   556  		return err
   557  	}
   558  	return nil
   559  }
   560  
   561  // drupalImportFilesAction defines the Drupal workflow for importing project files.
   562  func drupalImportFilesAction(app *DdevApp, uploadDir, importPath, extPath string) error {
   563  	destPath := app.calculateHostUploadDirFullPath(uploadDir)
   564  
   565  	// Parent of destination dir should exist
   566  	if !fileutil.FileExists(filepath.Dir(destPath)) {
   567  		return fmt.Errorf("unable to import to %s: parent directory does not exist", destPath)
   568  	}
   569  
   570  	// Parent of destination dir should be writable.
   571  	if err := os.Chmod(filepath.Dir(destPath), 0755); err != nil {
   572  		return err
   573  	}
   574  
   575  	// If the destination path exists, remove it as was warned
   576  	if fileutil.FileExists(destPath) {
   577  		if err := os.RemoveAll(destPath); err != nil {
   578  			return fmt.Errorf("failed to cleanup %s before import: %v", destPath, err)
   579  		}
   580  	}
   581  
   582  	if isTar(importPath) {
   583  		if err := archive.Untar(importPath, destPath, extPath); err != nil {
   584  			return fmt.Errorf("failed to extract provided archive: %v", err)
   585  		}
   586  
   587  		return nil
   588  	}
   589  
   590  	if isZip(importPath) {
   591  		if err := archive.Unzip(importPath, destPath, extPath); err != nil {
   592  			return fmt.Errorf("failed to extract provided archive: %v", err)
   593  		}
   594  
   595  		return nil
   596  	}
   597  
   598  	//nolint: revive
   599  	if err := fileutil.CopyDir(importPath, destPath); err != nil {
   600  		return err
   601  	}
   602  
   603  	return nil
   604  }
   605  
   606  // getDrupalComposerCreateAllowedPaths returns fullpaths that are allowed to be present when running composer create
   607  func getDrupalComposerCreateAllowedPaths(app *DdevApp) ([]string, error) {
   608  	var allowed []string
   609  
   610  	// Return early because we aren't expected to manage settings.
   611  	if app.DisableSettingsManagement {
   612  		return []string{}, nil
   613  	}
   614  
   615  	drupalConfig := NewDrupalSettings(app)
   616  
   617  	if app.Type == "drupal6" || app.Type == "drupal7" {
   618  		// drushrc.php path
   619  		allowed = append(allowed, filepath.Join(filepath.Dir(app.SiteSettingsPath), "drushrc.php"))
   620  	} else {
   621  		// Sync path
   622  		allowed = append(allowed, path.Join(app.GetDocroot(), "sites/default", drupalConfig.SyncDir))
   623  	}
   624  
   625  	return allowed, nil
   626  }