github.com/drud/ddev@v1.21.5-alpha1.0.20230226034409-94fcc4b94453/pkg/ddevapp/typo3.go (about) 1 package ddevapp 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "regexp" 8 "text/template" 9 10 "github.com/Masterminds/semver/v3" 11 "github.com/drud/ddev/pkg/archive" 12 "github.com/drud/ddev/pkg/fileutil" 13 "github.com/drud/ddev/pkg/nodeps" 14 "github.com/drud/ddev/pkg/output" 15 "github.com/drud/ddev/pkg/util" 16 ) 17 18 // createTypo3SettingsFile creates the app's LocalConfiguration.php and 19 // AdditionalConfiguration.php, adding things like database host, name, and 20 // password. Returns the fullpath to settings file and error 21 func createTypo3SettingsFile(app *DdevApp) (string, error) { 22 if filepath.Dir(app.SiteDdevSettingsFile) == app.AppRoot { 23 // As long as the final settings folder is not defined, early return 24 return app.SiteDdevSettingsFile, nil 25 } 26 27 if !fileutil.FileExists(app.SiteSettingsPath) { 28 util.Warning("TYPO3 does not seem to have been set up yet, missing %s (%s)", filepath.Base(app.SiteSettingsPath), app.SiteSettingsPath) 29 } 30 31 // TYPO3 ddev settings file will be AdditionalConfiguration.php (app.SiteDdevSettingsFile). 32 // Check if the file already exists. 33 if fileutil.FileExists(app.SiteDdevSettingsFile) { 34 // Check if the file is managed by ddev. 35 signatureFound, err := fileutil.FgrepStringInFile(app.SiteDdevSettingsFile, nodeps.DdevFileSignature) 36 if err != nil { 37 return "", err 38 } 39 40 // If the signature wasn't found, warn the user and return. 41 if !signatureFound { 42 util.Warning("%s already exists and is managed by the user.", filepath.Base(app.SiteDdevSettingsFile)) 43 return app.SiteDdevSettingsFile, nil 44 } 45 } 46 47 output.UserOut.Printf("Generating %s file for database connection.", filepath.Base(app.SiteDdevSettingsFile)) 48 if err := writeTypo3SettingsFile(app); err != nil { 49 return "", fmt.Errorf("failed to write TYPO3 AdditionalConfiguration.php file: %v", err.Error()) 50 } 51 52 return app.SiteDdevSettingsFile, nil 53 } 54 55 // writeTypo3SettingsFile produces AdditionalConfiguration.php file 56 // It's assumed that the LocalConfiguration.php already exists, and we're 57 // overriding the db config values in it. The typo3conf/ directory will 58 // be created if it does not yet exist. 59 func writeTypo3SettingsFile(app *DdevApp) error { 60 filePath := app.SiteDdevSettingsFile 61 62 // Ensure target directory is writable. 63 dir := filepath.Dir(filePath) 64 var perms os.FileMode = 0755 65 if err := os.Chmod(dir, perms); err != nil { 66 if !os.IsNotExist(err) { 67 // The directory exists, but chmod failed. 68 return err 69 } 70 71 // The directory doesn't exist, create it with the appropriate permissions. 72 if err := os.MkdirAll(dir, perms); err != nil { 73 return err 74 } 75 } 76 dbDriver := "mysqli" // mysqli is the driver used in default LocalConfiguration.php 77 if app.Database.Type == nodeps.Postgres { 78 dbDriver = "pdo_pgsql" 79 } 80 settings := map[string]interface{}{"DBHostname": "db", "DBDriver": dbDriver, "DBPort": GetExposedPort(app, "db")} 81 82 // Ensure target directory exists and is writable 83 if err := os.Chmod(dir, 0755); os.IsNotExist(err) { 84 if err = os.MkdirAll(dir, 0755); err != nil { 85 return err 86 } 87 } else if err != nil { 88 return err 89 } 90 91 f, err := os.Create(filePath) 92 if err != nil { 93 return err 94 } 95 96 t, err := template.New("AdditionalConfiguration.php").ParseFS(bundledAssets, "typo3/AdditionalConfiguration.php") 97 if err != nil { 98 return err 99 } 100 101 if err = t.Execute(f, settings); err != nil { 102 return err 103 } 104 if err != nil { 105 return err 106 } 107 return nil 108 } 109 110 // getTypo3UploadDir will return a custom upload dir if defined, returning a default path if not. 111 func getTypo3UploadDir(app *DdevApp) string { 112 if app.UploadDir == "" { 113 return "fileadmin" 114 } 115 116 return app.UploadDir 117 } 118 119 // Typo3Hooks adds a TYPO3-specific hooks example for post-import-db 120 const Typo3Hooks = `# post-start: 121 # - exec: composer install -d /var/www/html 122 ` 123 124 // getTypo3Hooks for appending as byte array 125 func getTypo3Hooks() []byte { 126 // We don't have anything new to add yet. 127 return []byte(Typo3Hooks) 128 } 129 130 // setTypo3SiteSettingsPaths sets the paths to settings files for templating 131 func setTypo3SiteSettingsPaths(app *DdevApp) { 132 settingsFileBasePath := filepath.Join(app.AppRoot, app.ComposerRoot) 133 var settingsFilePath, localSettingsFilePath string 134 135 if isTypo3v12OrHigher(app) { 136 settingsFilePath = filepath.Join(settingsFileBasePath, "config", "system", "settings.php") 137 localSettingsFilePath = filepath.Join(settingsFileBasePath, "config", "system", "additional.php") 138 } else if isTypo3App(app) { 139 settingsFilePath = filepath.Join(settingsFileBasePath, app.Docroot, "typo3conf", "LocalConfiguration.php") 140 localSettingsFilePath = filepath.Join(settingsFileBasePath, app.Docroot, "typo3conf", "AdditionalConfiguration.php") 141 } else { 142 // As long as TYPO3 is not installed, the file paths are set to the 143 // AppRoot to avoid the creation of the .gitignore in the wrong location. 144 settingsFilePath = filepath.Join(settingsFileBasePath, "LocalConfiguration.php") 145 localSettingsFilePath = filepath.Join(settingsFileBasePath, "AdditionalConfiguration.php") 146 } 147 148 // Update file paths 149 app.SiteSettingsPath = settingsFilePath 150 app.SiteDdevSettingsFile = localSettingsFilePath 151 } 152 153 // isTypoApp returns true if the app is of type typo3 154 func isTypo3App(app *DdevApp) bool { 155 typo3Folder := filepath.Join(app.AppRoot, app.Docroot, "typo3") 156 157 // Check if the folder exists, fails if a symlink target does not exist. 158 if _, err := os.Stat(typo3Folder); !os.IsNotExist(err) { 159 return true 160 } 161 162 // Check if a symlink exists, succeeds even if the target does not exist. 163 if _, err := os.Lstat(typo3Folder); !os.IsNotExist(err) { 164 return true 165 } 166 167 return false 168 } 169 170 // typo3ImportFilesAction defines the TYPO3 workflow for importing project files. 171 // The TYPO3 import-files workflow is currently identical to the Drupal workflow. 172 func typo3ImportFilesAction(app *DdevApp, importPath, extPath string) error { 173 destPath := app.GetHostUploadDirFullPath() 174 175 // parent of destination dir should exist 176 if !fileutil.FileExists(filepath.Dir(destPath)) { 177 return fmt.Errorf("unable to import to %s: parent directory does not exist", destPath) 178 } 179 180 // parent of destination dir should be writable. 181 if err := os.Chmod(filepath.Dir(destPath), 0755); err != nil { 182 return err 183 } 184 185 // If the destination path exists, remove it as was warned 186 if fileutil.FileExists(destPath) { 187 if err := os.RemoveAll(destPath); err != nil { 188 return fmt.Errorf("failed to cleanup %s before import: %v", destPath, err) 189 } 190 } 191 192 if isTar(importPath) { 193 if err := archive.Untar(importPath, destPath, extPath); err != nil { 194 return fmt.Errorf("failed to extract provided archive: %v", err) 195 } 196 197 return nil 198 } 199 200 if isZip(importPath) { 201 if err := archive.Unzip(importPath, destPath, extPath); err != nil { 202 return fmt.Errorf("failed to extract provided archive: %v", err) 203 } 204 205 return nil 206 } 207 208 //nolint: revive 209 if err := fileutil.CopyDir(importPath, destPath); err != nil { 210 return err 211 } 212 213 return nil 214 } 215 216 // isTypo3v12OrHigher returns true if the TYPO3 version is 12 or higher. The 217 // proper detection will fail if the vendor folder location is changed in the 218 // composer.json. 219 func isTypo3v12OrHigher(app *DdevApp) bool { 220 versionFilePath := filepath.Join(app.AppRoot, app.ComposerRoot, "vendor", "typo3", "cms-core", "Classes", "Information", "Typo3Version.php") 221 versionFile, err := fileutil.ReadFileIntoString(versionFilePath) 222 // Typo3Version class exists since v10.3.0. Before v11.5.0 the core was always 223 // installed into the folder public/typo3 so we can early return if the file 224 // is not found in the vendor folder. 225 if err != nil { 226 util.Debug("TYPO3 version class not found in '%s' for project %s, installed version is assumed to be older than 11.5.0: %v", versionFilePath, app.Name, err) 227 return false 228 } 229 230 // We may have a TYPO3 version 11 or higher and therefor have to parse the 231 // class file to properly detect the version. 232 re := regexp.MustCompile(`const\s+VERSION\s*=\s*'([^']+)`) 233 234 matches := re.FindStringSubmatch(versionFile) 235 236 if len(matches) < 2 { 237 util.Warning("Unexpected Typo3Version found for project %s in %v.", app.Name, versionFile) 238 return false 239 } 240 241 version, err := semver.NewVersion(matches[1]) 242 if err != nil { 243 // This case never should happen 244 util.Warning("Unexpected error while parsing TYPO3 version ('%s') for project %s: %v.", matches[1], app.Name, err) 245 return false 246 } 247 248 util.Debug("Found TYPO3 version %v for project %s.", version.Original(), app.Name) 249 250 return version.Major() >= 12 251 }