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

     1  package ddevapp
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"regexp"
     8  
     9  	"github.com/ddev/ddev/pkg/archive"
    10  	"github.com/ddev/ddev/pkg/fileutil"
    11  	"github.com/ddev/ddev/pkg/nodeps"
    12  	"github.com/ddev/ddev/pkg/util"
    13  )
    14  
    15  // isCraftCmsApp returns true if the app is of type craftcms
    16  func isCraftCmsApp(app *DdevApp) bool {
    17  	return fileutil.FileExists(filepath.Join(app.AppRoot, app.ComposerRoot, "craft"))
    18  }
    19  
    20  // craftCmsImportFilesAction defines the workflow for importing project files.
    21  func craftCmsImportFilesAction(app *DdevApp, uploadDir, importPath, extPath string) error {
    22  	destPath := app.calculateHostUploadDirFullPath(uploadDir)
    23  
    24  	// parent of destination dir should exist
    25  	if !fileutil.FileExists(filepath.Dir(destPath)) {
    26  		return fmt.Errorf("unable to import to %s: parent directory does not exist", destPath)
    27  	}
    28  
    29  	// parent of destination dir should be writable.
    30  	if err := os.Chmod(filepath.Dir(destPath), 0755); err != nil {
    31  		return err
    32  	}
    33  
    34  	// If the destination path exists, remove it as was warned
    35  	if fileutil.FileExists(destPath) {
    36  		if err := os.RemoveAll(destPath); err != nil {
    37  			return fmt.Errorf("failed to cleanup %s before import: %v", destPath, err)
    38  		}
    39  	}
    40  
    41  	if isTar(importPath) {
    42  		if err := archive.Untar(importPath, destPath, extPath); err != nil {
    43  			return fmt.Errorf("failed to extract provided archive: %v", err)
    44  		}
    45  
    46  		return nil
    47  	}
    48  
    49  	if isZip(importPath) {
    50  		if err := archive.Unzip(importPath, destPath, extPath); err != nil {
    51  			return fmt.Errorf("failed to extract provided archive: %v", err)
    52  		}
    53  
    54  		return nil
    55  	}
    56  
    57  	//nolint: revive
    58  	if err := fileutil.CopyDir(importPath, destPath); err != nil {
    59  		return err
    60  	}
    61  
    62  	return nil
    63  }
    64  
    65  // Set up the .env file for ddev
    66  func craftCmsPostStartAction(app *DdevApp) error {
    67  	// If settings management is disabled, do nothing
    68  	if app.DisableSettingsManagement {
    69  		return nil
    70  	}
    71  
    72  	// Check version is v4 or higher or warn user about app type mismatch.
    73  	if !isCraftCms4orHigher(app) {
    74  		util.Warning("It looks like the installed Craft CMS is lower than version 4 where it's recommended to use project type `php` or disable settings management with `ddev config --disable-settings-management`")
    75  		if !util.Confirm("Would you like to stop here, not do the automatic configuration and change project type?") {
    76  			return nil
    77  		}
    78  	}
    79  
    80  	// If the .env file doesn't exist, try to create it by copying .env.example to .env
    81  	envFilePath := filepath.Join(app.AppRoot, app.ComposerRoot, ".env")
    82  	if !fileutil.FileExists(envFilePath) {
    83  		var exampleEnvFilePaths = []string{".env.example", ".env.example.dev"}
    84  		for _, envFileName := range exampleEnvFilePaths {
    85  			exampleEnvFilePath := filepath.Join(app.AppRoot, app.ComposerRoot, envFileName)
    86  			if fileutil.FileExists(exampleEnvFilePath) {
    87  				util.Success(fmt.Sprintf("Copied %s to .env", envFileName))
    88  				err := fileutil.CopyFile(exampleEnvFilePath, envFilePath)
    89  				if err != nil {
    90  					util.Error(fmt.Sprintf("Error copying %s to .env", exampleEnvFilePath))
    91  
    92  					return err
    93  				}
    94  			}
    95  		}
    96  	}
    97  	// If the .env file *still* doesn't exist, return early
    98  	if !fileutil.FileExists(envFilePath) {
    99  		return nil
   100  	}
   101  	// Read in the .env file
   102  	envMap, envText, err := ReadProjectEnvFile(envFilePath)
   103  	if err != nil && !os.IsNotExist(err) {
   104  		return fmt.Errorf("unable to read .env file: %v", err)
   105  	}
   106  
   107  	port := "3306"
   108  	driver := "mysql"
   109  	if app.Database.Type == nodeps.Postgres {
   110  		driver = "pgsql"
   111  		port = "5432"
   112  	}
   113  
   114  	// If they have older version of .env with DB_DRIVER, DB_SERVER etc, use those
   115  	if _, ok := envMap["DB_SERVER"]; ok {
   116  		// TODO: Remove, was never an official standard of Craft CMS.
   117  		envMap = map[string]string{
   118  			"DB_DRIVER":             driver,
   119  			"DB_SERVER":             "db",
   120  			"DB_PORT":               port,
   121  			"DB_DATABASE":           "db",
   122  			"DB_USER":               "db",
   123  			"DB_PASSWORD":           "db",
   124  			"MAILPIT_SMTP_HOSTNAME": "127.0.0.1",
   125  			"MAILPIT_SMTP_PORT":     "1025",
   126  			"PRIMARY_SITE_URL":      app.GetPrimaryURL(),
   127  		}
   128  	} else {
   129  		// Otherwise use the current CRAFT_DB_SERVER etc.
   130  		envMap = map[string]string{
   131  			"CRAFT_DB_DRIVER":       driver,
   132  			"CRAFT_DB_SERVER":       "db",
   133  			"CRAFT_DB_PORT":         port,
   134  			"CRAFT_DB_DATABASE":     "db",
   135  			"CRAFT_DB_USER":         "db",
   136  			"CRAFT_DB_PASSWORD":     "db",
   137  			"CRAFT_WEB_ROOT":        app.GetAbsDocroot(true),
   138  			"MAILPIT_SMTP_HOSTNAME": "127.0.0.1",
   139  			"MAILPIT_SMTP_PORT":     "1025",
   140  			"PRIMARY_SITE_URL":      app.GetPrimaryURL(),
   141  		}
   142  	}
   143  
   144  	err = WriteProjectEnvFile(envFilePath, envMap, envText)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func craftCmsConfigOverrideAction(app *DdevApp) error {
   153  	app.Database = DatabaseDesc{nodeps.MySQL, nodeps.MySQL80}
   154  	return nil
   155  }
   156  
   157  // isCraftCms4orHigher returns true if the Craft CMS version is 4 or higher. The
   158  // proper detection will fail if the vendor folder location is changed in the
   159  // composer.json.
   160  // The detection is based on a change starting with 4.0.0-RC1 where deprecated
   161  // constants were removed in src/Craft.php see
   162  // https://github.com/craftcms/cms/commit/1660ff90a3a69cec425271d47ade66523a4bd44e#diff-21e22a30e7c48265a4dcedc1b1c8b9372eca5d3fdeff6d72c7d9c6b671365c56
   163  func isCraftCms4orHigher(app *DdevApp) bool {
   164  	craftFilePath := filepath.Join(app.GetComposerRoot(false, false), "vendor", "craftcms", "cms", "src", "Craft.php")
   165  	if !fileutil.FileExists(craftFilePath) {
   166  		// Sources are not installed, assuming v4 or higher.
   167  		return true
   168  	}
   169  
   170  	craftFileContent, err := fileutil.ReadFileIntoString(craftFilePath)
   171  	if err != nil {
   172  		util.Warning("unable to read file `%s` in project `%s`: %v", craftFilePath, app.Name, err)
   173  
   174  		return true
   175  	}
   176  
   177  	return !regexp.MustCompile(`const\s+Personal\s*=\s*0`).MatchString(craftFileContent)
   178  }