├── .github └── workflows │ └── deploy-infrastructure.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── standards-and-practices │ └── standards-and-conventions.md └── technical-guides │ └── deploy-wordpress-azure-vm.md └── src ├── app ├── Dovs.WordPressAutoKit.Suite.sln └── Dovs.WordPressAutoKit │ ├── Common │ └── ElementIds.cs │ ├── Dovs.WordPressAutoKit.csproj │ ├── Interfaces │ ├── IAdminLoginService.cs │ ├── IAuthenticationService.cs │ ├── IMembershipService.cs │ ├── IPasswordService.cs │ ├── IRoleService.cs │ ├── IUserManagementService.cs │ └── IWebDriverService.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Services │ ├── AdminLoginService.cs │ ├── AuthenticationService.cs │ ├── MembershipService.cs │ ├── PasswordService.cs │ ├── RoleService.cs │ ├── UserManagementService.cs │ └── WebDriverService.cs │ ├── UserData.cs │ ├── appsettings.Release.json │ └── appsettings.json └── iac ├── environments └── pre-prod │ ├── locals.tf │ ├── main.tf │ ├── providers.tf │ └── variables.tf ├── modules ├── compute │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── network │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── resource-group │ ├── main.tf │ ├── outputs.tf │ └── varibales.tf └── security │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── scripts └── install-lamp.sh /.github/workflows/deploy-infrastructure.yml: -------------------------------------------------------------------------------- 1 | name: Terraform Azure Deployment 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | env: 7 | WORKING_DIRECTORY: src/iac/environments/pre-prod 8 | 9 | jobs: 10 | terraform: 11 | name: 'Terraform' 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | TF_VAR_workload: "learning" 16 | TF_VAR_environment: "test" 17 | TF_VAR_location: "uksouth" 18 | TF_VAR_instance: "001" 19 | TF_VAR_provisioner_script: "../../scripts/install-lamp.sh" 20 | 21 | defaults: 22 | run: 23 | shell: bash 24 | working-directory: ${{ env.WORKING_DIRECTORY }} 25 | 26 | steps: 27 | - name: 'Checkout GitHub Action' 28 | uses: actions/checkout@v2 29 | 30 | - name: 'Set up Terraform' 31 | uses: hashicorp/setup-terraform@v3 32 | with: 33 | terraform_version: "1.9.6" 34 | 35 | - name: Authenticate with Azure 36 | uses: azure/login@v1 37 | with: 38 | creds: ${{ secrets.AZURE_CREDENTIALS }} 39 | 40 | - name: 'Terraform Format Check' 41 | id: fmt 42 | run: terraform fmt -check 43 | continue-on-error: true 44 | 45 | - name: 'Terraform Init' 46 | run: terraform init 47 | 48 | - name: 'Terraform Validate' 49 | id: validate 50 | run: terraform validate -no-color 51 | 52 | - name: 'Debug Variables' 53 | run: | 54 | echo "Workload: ${{ env.TF_VAR_workload }}" 55 | echo "Environment: ${{ env.TF_VAR_environment }}" 56 | echo "Location: ${{ env.TF_VAR_location }}" 57 | echo "Instance: ${{ env.TF_VAR_instance }}" 58 | echo "Provisioner Script: ${{ env.TF_VAR_provisioner_script }}" 59 | 60 | - name: 'Terraform Plan' 61 | env: 62 | ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} 63 | ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} 64 | ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} 65 | ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} 66 | run: | 67 | terraform plan \ 68 | -var="workload=${{ env.TF_VAR_workload }}" \ 69 | -var="environment=${{ env.TF_VAR_environment }}" \ 70 | -var="location=${{ env.TF_VAR_location }}" \ 71 | -var="instance=${{ env.TF_VAR_instance }}" \ 72 | -var="admin_username=${{ secrets.ADMIN_USERNAME }}" \ 73 | -var="admin_password=${{ secrets.ADMIN_PASSWORD }}" \ 74 | -var="provisioner_script=${{ env.TF_VAR_provisioner_script }}" 75 | 76 | - name: 'Terraform Apply' 77 | env: 78 | ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} 79 | ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} 80 | ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} 81 | ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} 82 | run: | 83 | terraform apply -auto-approve \ 84 | -var="workload=${{ env.TF_VAR_workload }}" \ 85 | -var="environment=${{ env.TF_VAR_environment }}" \ 86 | -var="location=${{ env.TF_VAR_location }}" \ 87 | -var="instance=${{ env.TF_VAR_instance }}" \ 88 | -var="admin_username=${{ secrets.ADMIN_USERNAME }}" \ 89 | -var="admin_password=${{ secrets.ADMIN_PASSWORD }}" \ 90 | -var="provisioner_script=${{ env.TF_VAR_provisioner_script }}" 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | # Ignore build results and temporary files for Dovs.FileSystemInteractor 401 | src/app/Dovs.WordPressAutoKit/Libs 402 | 403 | # Local .terraform directories 404 | **/.terraform/* 405 | 406 | # .tfstate files 407 | *.tfstate 408 | *.tfstate.* 409 | 410 | # Crash log files 411 | crash.log 412 | crash.*.log 413 | 414 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 415 | # password, private keys, and other secrets. These should not be part of version 416 | # control as they are data points which are potentially sensitive and subject 417 | # to change depending on the environment. 418 | *.tfvars 419 | *.tfvars.json 420 | 421 | # Ignore override files as they are usually used to override resources locally and so 422 | # are not checked in 423 | override.tf 424 | override.tf.json 425 | *_override.tf 426 | *_override.tf.json 427 | 428 | # Ignore transient lock info files created by terraform apply 429 | .terraform.tfstate.lock.info 430 | 431 | .terraform.lock.hcl 432 | 433 | # Include override files you do wish to add to version control using negated pattern 434 | # !example_override.tf 435 | 436 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 437 | # example: *tfplan* 438 | 439 | # Ignore CLI configuration files 440 | .terraformrc 441 | terraform.rc 442 | 443 | # Ignore Debug settings 444 | appsettings.Debug.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 DevOpsVisions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress Learning Automation Toolkit 2 | 3 | ## Overview 4 | 5 | WordPress Learning Automation Toolkit is a .NET Core application designed to automate various tasks using Selenium WebDriver and other services. 6 | The project includes services for configuration management, file path handling, authentication, password management, web driver operations, Excel reading, and user management. 7 | 8 | ## Purpose 9 | 10 | The primary purpose of this project is to provide a comprehensive automation framework that can be used to automate repetitive tasks, such as adding users, removing users, membership updates, and data extraction from Excel files. 11 | By leveraging Selenium WebDriver, the application can interact with web pages and perform several actions like a human user. This makes it ideal for tasks that require web interaction, such as automated workflow data scraping, and web form submissions. 12 | 13 | ## Features 14 | - **Adding Users:** Automates the process of adding new users to the platform. 15 | - **Removing Users:** Automates the process of deleting users from the platform. 16 | - **Membership Updates:** Automates the process of updating user memberships. 17 | - **Reads User Data:** Extracts user information, such as username and email, directly from an Excel file, streamlining the registration process. 18 | - **Secure Password Handling:** Ensures that passwords are handled securely during the registration process, protecting sensitive user information. 19 | - **Flexible File Path Support:** Supports both default and custom file paths for the Excel file, allowing users to easily manage and update user data sources. 20 | 21 | ## Prerequisites 22 | 23 | - [.NET Core SDK](https://dotnet.microsoft.com/download) 24 | - [Selenium WebDriver](https://www.selenium.dev/) (ChromeDriver) 25 | - [ExcelDataReader](https://github.com/ExcelDataReader/ExcelDataReader) library for reading Excel files. 26 | - Google Chrome browser installed. -------------------------------------------------------------------------------- /docs/standards-and-practices/standards-and-conventions.md: -------------------------------------------------------------------------------- 1 | # Standards and Conventions 2 | 3 | All standards and conventions outlined here are based on our established guidelines, which can be found in the following repository: 4 | 5 | [Common Workspace Hub](https://github.com/DevOpsVisions/common-workspace-hub) 6 | 7 | ## Repository and Application 8 | 9 | - **Repository Name**: `wordpress-automation-toolkit` 10 | - **Application Name**: `WordPress AutoKit` 11 | - **Executable Name**: `WordPressAutoKit` 12 | 13 | ## Solution and Projects 14 | 15 | - **Solution File**: `Dovs.WordPressAutoKit.Suite` 16 | - **Project Files**: 17 | - `Dovs.WordPressAutoKit`: The main project file for the core application. 18 | - `Dovs.WordPressAutoKit.UnitTest`: The project file dedicated to unit tests. 19 | - `Dovs.WordPressAutoKit.IntTest`: The project file dedicated to integration tests. 20 | 21 | ## Azure Cloud 22 | **Testing Environment:** 23 | - **Resource Name**: `rg-wordpress-test-uksouth-001` 24 | - **Workload/Application**: WordPress 25 | - **Environment**: Test 26 | - **Azure Region**: UK South 27 | - **Instance**: 001 28 | ## Release Notes (Example) 29 | 30 | |

**Aug 06**

**[@Mohamed Radwan]()**

**[20240514.1]()**

**[ce53bd8]()**
 

 

 

 

 

 

|

**WordPress AutoKit 1.6.1**

[DevOpsVisions]() released this 2 weeks ago   [v1.6.1]()   [2c537d3]()

1.6.1 (06-08-2024)

**Features Added:**

- Added a new read-only property ([Issue #345](), [PR #254]())

**Bugs Fixed:**

- First row MS Excel error ([Issue #123](), [PR #237]())

**Assets**

[App (zip)]()                                                            06 August

[Source code (zip)]()                                               06 August

| 31 | | :- | :- | 32 | 33 | -------------------------------------------------------------------------------- /docs/technical-guides/deploy-wordpress-azure-vm.md: -------------------------------------------------------------------------------- 1 | 2 | # Deploying or Restoring WordPress on an Azure VM 3 | 4 | This guide provides a comprehensive walkthrough for setting up a testing environment on Azure by deploying or restoring WordPress on an Ubuntu VM. The instructions cover preparing a LAMP stack environment, configuring essential components, and deploying the WordPress application. Follow these steps to ensure a smooth deployment and configuration process for testing purposes. 5 | 6 | ## 1. Preparing a LAMP Stack Environment 7 | 8 | To prepare your environment, refer to the following guide: 9 | 10 | [Preparing a LAMP Stack Environment for Hosting](https://github.com/DevOpsVisions/common-workspace-hub/blob/main/docs/technical-guides/prepare-lamp-stack-env.md) 11 | 12 | ## 2. Download and Set Up WordPress 13 | 14 | Download the WordPress zip file, whether it's the latest version or a backup from your production environment. 15 | 16 | - **Extract the WordPress site from the backup file:** 17 | 18 | ```bash 19 | unar /home/azureuser/Downloads/backup.tgz 20 | ``` 21 | The `unar` command extracts the contents of `backup.tgz` to the current directory. Make sure you have the `unar` utility installed. 22 | 23 | ## 3. Restore WordPress Files 24 | 25 | - **Remove all files in the web root directory:** 26 | 27 | ```bash 28 | sudo rm -r /var/www/html/* 29 | ``` 30 | *Deletes all existing files in the web server's root directory (`/var/www/html`) to prepare for the restoration.* 31 | 32 | - **Copy the backup files to the web root:** 33 | 34 | ```bash 35 | sudo cp -a /home/azureuser/backup/public_html/. /var/www/html 36 | ``` 37 | *Copies the restored WordPress files from the backup location to the web server's root directory.* 38 | 39 | - **Adjust ownership and permissions:** 40 | 41 | ```bash 42 | sudo chown -R www-data:www-data /var/www/html/ 43 | ``` 44 | ```bash 45 | sudo find /var/www/html -type d -exec chmod 755 {} \; 46 | ``` 47 | ```bash 48 | sudo find /var/www/html -type f -exec chmod 644 {} \; 49 | ``` 50 | *Sets the correct ownership (`www-data`) and permissions (755 for directories, 644 for files) to ensure the web server can access the files properly.* 51 | 52 | ## 4. Configure MySQL Database 53 | 54 | - **Log in to MySQL:** Log in to MySQL with the root user. 55 | 56 | ```bash 57 | sudo mysql -p 58 | ``` 59 | *This command opens the MySQL command line interface. The `-p` option prompts you to enter your MySQL root password.* 60 | 61 | - **List all MySQL users:** 62 | 63 | ```sql 64 | SELECT User, Host FROM mysql.user; 65 | ``` 66 | *This SQL query displays all existing users and their host permissions in the MySQL server.* 67 | 68 | - **Create a new MySQL user for WordPress:** 69 | 70 | ```sql 71 | CREATE USER 'wordpressUser'@'localhost' IDENTIFIED BY 'YourStrongPassword'; 72 | ``` 73 | *Creates a new MySQL user `wordpressUser` with a strong password, restricted to the local host.* 74 | 75 | > [!IMPORTANT] 76 | > Ensure you use a secure password. 77 | 78 | - **Check existing databases:** 79 | 80 | ```sql 81 | SHOW DATABASES; 82 | ``` 83 | *Lists all databases on the MySQL server to ensure there is no conflict with the new database name.* 84 | 85 | - **Create a new WordPress database:** 86 | 87 | ```sql 88 | CREATE DATABASE wordpress; 89 | ``` 90 | ```sql 91 | SHOW DATABASES; 92 | ``` 93 | *Creates a new database named `wordpress` and lists all databases to confirm its creation.* 94 | 95 | - **Grant all privileges to the new user:** 96 | 97 | ```sql 98 | GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpressUser'@'localhost'; 99 | ``` 100 | ```sql 101 | FLUSH PRIVILEGES; 102 | ``` 103 | 104 | *Grants full privileges to `wordpressUser` on the `wordpress` database and applies the changes with `FLUSH PRIVILEGES`.* 105 | 106 | ## 5. Configure WordPress Database 107 | 108 | - **Import Database (Optional):** If you are restoring from a production environment backup, use the following commands to import the database: 109 | 110 | ```sql 111 | USE wordpress; 112 | ``` 113 | ```sql 114 | source /home/azureuser/backup/backup.sql; 115 | ``` 116 | *Imports the SQL backup file into the newly created `wordpress` database.* 117 | 118 | - **Update the site URL and home URL:** 119 | 120 | ```sql 121 | UPDATE wp_options SET option_value = 'http://test.example.com/' WHERE option_name = 'siteurl'; 122 | ``` 123 | ```sql 124 | UPDATE wp_options SET option_value = 'http://test.example.com/' WHERE option_name = 'home'; 125 | ``` 126 | ```sql 127 | exit 128 | ``` 129 | *Modifies the WordPress site URLs to match the new domain or IP address.* 130 | 131 | - **Open the `wp-config.php` file for editing:** 132 | 133 | ```bash 134 | sudo nano /var/www/html/wp-config.php 135 | ``` 136 | *Uses the `nano` text editor to open the WordPress configuration file. Update database settings to match the new database and user created earlier.* 137 | 138 | ## 6. Remove Unnecessary Plugins 139 | 140 | - **Delete the unnecessary plugin:** 141 | 142 | ```bash 143 | sudo rm -r /var/www/html/wp-content/plugins/object-cache-pro 144 | ``` 145 | *Removes the `object-cache-pro` plugin if it is not required.* 146 | 147 | ## 7. Install Required Extensions 148 | 149 | If your WordPress site includes the MasterStudy LMS plugin, the `mbstring` extension is required for proper functionality. 150 | 151 | ```bash 152 | sudo apt-get install php-mbstring 153 | ``` 154 | The `php-mbstring` extension is necessary for handling multibyte string operations, especially in the context of LMS plugins like MasterStudy. The installation is performed using `apt-get`, the package manager for Ubuntu/Debian systems. 155 | 156 | ## 8. Configure Apache 157 | 158 | - **Enable `rewrite` module:** 159 | 160 | ```bash 161 | sudo a2enmod rewrite 162 | ``` 163 | *Enables the Apache rewrite module to allow URL rewriting, which is often necessary for WordPress permalinks.* 164 | 165 | - **Restart Apache:** 166 | 167 | ```bash 168 | sudo systemctl restart apache2 169 | ``` 170 | *Restarts the Apache server to apply changes.* 171 | 172 | - **Open Apache configuration file:** 173 | 174 | ```bash 175 | sudo nano /etc/apache2/apache2.conf 176 | ``` 177 | *Opens the Apache configuration file for editing.* 178 | 179 | - **Edit and ensure the following configuration exists:** 180 | ``` 181 | 182 | Options Indexes FollowSymLinks 183 | AllowOverride All 184 | Require all granted 185 | 186 | ``` 187 | *Allows Apache to override settings with `.htaccess` files and ensures proper access control.* 188 | 189 | - **Restart Apache:** 190 | 191 | ```bash 192 | sudo systemctl restart apache2 193 | ``` 194 | *Restarts the Apache server to apply the new configuration.* 195 | 196 | ## 9. Install WP-CLI 197 | 198 | - **Download WP-CLI:** as we need it later in the next step 199 | 200 | ```bash 201 | curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 202 | ``` 203 | *Downloads the WP-CLI executable for managing WordPress installations via command line.* 204 | 205 | - **Check WP-CLI information:** 206 | 207 | ```bash 208 | php wp-cli.phar --info 209 | ``` 210 | *Verifies the installation and configuration of WP-CLI.* 211 | 212 | - **Make WP-CLI executable and move it:** 213 | 214 | ```bash 215 | chmod +x wp-cli.phar 216 | ``` 217 | ```bash 218 | sudo mv wp-cli.phar /usr/local/bin/wp 219 | ``` 220 | ```bash 221 | wp --info 222 | ``` 223 | *Makes the WP-CLI file executable and moves it to a directory in the system's `PATH` for global access.* 224 | 225 | ## 10. Replace URLs and Flush Cache 226 | 227 | - **Navigate to the WordPress installation directory:** 228 | 229 | ```bash 230 | cd /var/www/html/ 231 | ``` 232 | *Changes the current working directory to the WordPress root directory.* 233 | 234 | - **Replace all URLs:** 235 | 236 | ```bash 237 | wp search-replace 'https://example.com/' 'http://test.example.com/' --all-tables 238 | ``` 239 | ```bash 240 | wp cache flush 241 | ``` 242 | *Replaces old URLs with the new ones across all database tables and clears the WordPress cache.* 243 | 244 | ## 11. Adjust Plugin Permissions 245 | 246 | - **Correct plugin ownership and permissions:** 247 | 248 | ```bash 249 | sudo chown -R www-data:www-data /var/www/html/wp-content/plugins/ 250 | ``` 251 | ```bash 252 | sudo chmod -R 755 /var/www/html/wp-content/plugins/ 253 | ``` 254 | *Ensures the correct ownership and permissions are set for the plugins directory to avoid permission errors.* 255 | 256 | ## 12. Clean Up Unnecessary Plugins 257 | 258 | - **Remove unnecessary plugins from the WordPress admin panel.** Log in to the WordPress admin dashboard and delete any unused plugins to maintain a clean and secure environment.* 259 | 260 | ## 12. Final Checks and Testing 261 | 262 | **Test the WordPress site:** 263 | 264 | - Navigate to the site's URL in your web browser to ensure it is loading correctly. 265 | - Log in to the WordPress admin dashboard and verify that all settings and plugins are configured as expected. 266 | - Check the functionality of the MasterStudy LMS plugin to ensure it is working properly. 267 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit.Suite.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35219.272 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dovs.WordPressAutoKit", "Dovs.WordPressAutoKit\Dovs.WordPressAutoKit.csproj", "{848690B1-B44C-45F5-BF13-5F66F259FDF6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {848690B1-B44C-45F5-BF13-5F66F259FDF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {848690B1-B44C-45F5-BF13-5F66F259FDF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {848690B1-B44C-45F5-BF13-5F66F259FDF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {848690B1-B44C-45F5-BF13-5F66F259FDF6}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {984B97A8-6949-47C5-BB94-B7E1D839DEF7} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Common/ElementIds.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium.DevTools.V125.Storage; 2 | 3 | namespace Dovs.WordPressAutoKit.Common 4 | { 5 | /// 6 | /// Contains constants for HTML element IDs used in the application. 7 | /// 8 | public static class ElementIds 9 | { 10 | /// 11 | /// The HTML element ID for the username (user_login) field in the registration form. 12 | /// 13 | public const string USER_NAME_INPUT = "user_login"; 14 | 15 | /// 16 | /// The HTML element ID for the password (user_pass) field in the login form. 17 | /// 18 | public const string USER_PASS_INPUT = "user_pass"; 19 | 20 | /// 21 | /// The HTML element ID for the login button in the login form. 22 | /// 23 | public const string LOGIN_BUTTON = "wp-submit"; 24 | 25 | /// 26 | /// The HTML element ID for the email field in the registration form. 27 | /// 28 | public const string EMAIL_INPUT = "email"; 29 | 30 | /// 31 | /// The HTML element ID for the first name field in the registration form. 32 | /// 33 | public const string FIRST_NAME_INPUT = "first_name"; 34 | 35 | /// 36 | /// The HTML element ID for the last name field in the registration form. 37 | /// 38 | public const string LAST_NAME_INPUT = "last_name"; 39 | 40 | /// 41 | /// The HTML element ID for the new user password field in the registration form. 42 | /// 43 | public const string NEW_USER_PASSWORD_INPUT = "pass1"; 44 | 45 | /// 46 | /// The HTML element ID for the role selection dropdown in the registration form. 47 | /// 48 | public const string ROLE_SELECTION = "role"; 49 | 50 | /// 51 | /// The HTML element ID for the create user button in the registration form. 52 | /// 53 | public const string CREATE_USER_BUTTON = "createusersub"; 54 | 55 | /// 56 | /// The HTML element ID for the confirmation message div. 57 | /// 58 | public const string CONFIRMATION_DIV = "message"; 59 | 60 | /// 61 | /// The HTML element ID for the first button to add membership. 62 | /// 63 | public const string ADD_MEMBERSHIP_FIRST_BUTTON = "a.button.button-secondary[href*='pmpro_member_edit_panel=memberships']"; 64 | 65 | /// 66 | /// The HTML element ID for the second button to add membership. 67 | /// 68 | public const string ADD_MEMBERSHIP_SECOND_BUTTON = "a.pmpro-member-change-level"; 69 | 70 | /// 71 | /// The HTML element ID for the save button to add membership. 72 | /// 73 | public const string ADD_MEMBERSHIP_SAVE_BUTTON = "button.button.button-primary"; 74 | 75 | /// 76 | /// The HTML element ID for the membership level dropdown. 77 | /// 78 | public const string MEMBERSHIP_LEVEL_DROP = "pmpro-member-edit-memberships-panel-add_level_to_group_1[level_id]"; 79 | 80 | /// 81 | /// The HTML element ID for the update user button. 82 | /// 83 | public const string UPDATE_USER_BUTTON = "submit"; 84 | 85 | /// 86 | /// The HTML tag name for anchor tags. 87 | /// 88 | public const string ANCHOR_TAG_NAME = "a"; 89 | 90 | /// 91 | /// The HTML attribute name for the href attribute in anchor tags. 92 | /// 93 | public const string ANCHOR_TAG_ATTRIBUTE = "href"; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Dovs.WordPressAutoKit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | Exe 6 | net8.0 7 | enable 8 | enable 9 | WordPress AutoKit 10 | A toolkit for automating WordPress tasks 11 | DevOps Visions 12 | WordPress AutoKit 13 | Copyright © DevOps Visions 2024 14 | 1.0.0.0 15 | 1.0.0.0 16 | WordPressAutoKit 17 | True 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | PreserveNewest 38 | 39 | 40 | 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | 47 | 48 | 49 | PreserveNewest 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IAdminLoginService.cs: -------------------------------------------------------------------------------- 1 | 2 | using OpenQA.Selenium; 3 | 4 | namespace Dovs.WordPressAutoKit.Interfaces 5 | { 6 | /// 7 | /// Interface for admin login service. 8 | /// 9 | public interface IAdminLoginService 10 | { 11 | /// 12 | /// Logs in to the admin panel using the provided WebDriver, username, and password. 13 | /// 14 | /// The WebDriver instance used to perform the login. 15 | /// The admin username. 16 | /// The admin password. 17 | void Login(IWebDriver driver, string loginUrl, string username, string password); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IAuthenticationService.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Dovs.WordPressAutoKit.Interfaces 3 | { 4 | /// 5 | /// Interface for authentication service. 6 | /// 7 | public interface IAuthenticationService 8 | { 9 | /// 10 | /// Gets the admin username for login. 11 | /// 12 | /// The admin username. 13 | string GetAdminUsername(string adminUserNames); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IMembershipService.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Dovs.WordPressAutoKit.Interfaces 4 | { 5 | /// 6 | /// Interface for membership service. 7 | /// 8 | public interface IMembershipService 9 | { 10 | /// 11 | /// Updates the membership level of a user. 12 | /// 13 | /// The WebDriver instance used to perform the update. 14 | /// The new membership level to be assigned. 15 | void AddMembership(IWebDriver driver, string membershipLevel); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IPasswordService.cs: -------------------------------------------------------------------------------- 1 | namespace Dovs.WordPressAutoKit.Interfaces 2 | { 3 | /// 4 | /// Interface for password service. 5 | /// 6 | public interface IPasswordService 7 | { 8 | /// 9 | /// Prompts the user for a password with the specified prompt message. 10 | /// 11 | /// The prompt message to display to the user. 12 | /// The password entered by the user. 13 | string PromptForPassword(string prompt); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IRoleService.cs: -------------------------------------------------------------------------------- 1 |  2 | using OpenQA.Selenium; 3 | 4 | namespace Dovs.WordPressAutoKit.Interfaces 5 | { 6 | public interface IRoleService 7 | { 8 | void UpdateRole(IWebDriver driver, string role); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IUserManagementService.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Dovs.WordPressAutoKit.Interfaces 4 | { 5 | /// 6 | /// Interface for user management service. 7 | /// 8 | public interface IUserManagementService 9 | { 10 | /// 11 | /// Adds a new user to the system. 12 | /// 13 | /// The WebDriver instance used to perform the operation. 14 | /// The data of the user to be added. 15 | /// The password for the new user. 16 | void AddNewUser(IWebDriver driver, UserData userData, string password, string addNewUserUrl); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Interfaces/IWebDriverService.cs: -------------------------------------------------------------------------------- 1 | using OpenQA.Selenium; 2 | 3 | namespace Dovs.WordPressAutoKit.Interfaces 4 | { 5 | /// 6 | /// Interface for WebDriver service. 7 | /// 8 | public interface IWebDriverService 9 | { 10 | /// 11 | /// Creates a new WebDriver instance. 12 | /// 13 | /// A new instance of IWebDriver. 14 | IWebDriver CreateWebDriver(); 15 | 16 | /// 17 | /// Quits the specified WebDriver instance. 18 | /// 19 | /// The WebDriver instance to quit. 20 | void QuitWebDriver(IWebDriver driver); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Program.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | using Dovs.WordPressAutoKit.Services; 3 | using Dovs.FileSystemInteractor.Interfaces; 4 | using Dovs.FileSystemInteractor.Services; 5 | using Dovs.CommonComponents; 6 | using OpenQA.Selenium; 7 | using Dovs.WordPressAutoKit; 8 | using Serilog; 9 | using System.Reflection; 10 | using System.Xml.Linq; 11 | using System.Text.RegularExpressions; 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | try 17 | { 18 | InitializeServices(); 19 | ConfigureLogging(); 20 | 21 | if (args.Length > 0 && args[0].Equals("add-user", StringComparison.OrdinalIgnoreCase)) 22 | { 23 | if (args.Contains("--help", StringComparer.OrdinalIgnoreCase) || args.Contains("-h", StringComparer.OrdinalIgnoreCase)) 24 | { 25 | DisplayHelp("M:Program.AddUsers(System.String,System.String,System.String,System.String)", "add-user"); 26 | return; 27 | } 28 | if (args.Length == 9) 29 | { 30 | string filePath = GetArgumentValue(args, "--file-path", "-fp"); 31 | string adminUsername = GetArgumentValue(args, "--admin-username", "-aun"); 32 | string adminPassword = GetArgumentValue(args, "--admin-password", "-apass"); 33 | string registrationPassword = GetArgumentValue(args, "--registration-password", "-rpass"); 34 | 35 | if (!string.IsNullOrEmpty(filePath) && !string.IsNullOrEmpty(adminUsername) && !string.IsNullOrEmpty(adminPassword) && !string.IsNullOrEmpty(registrationPassword)) 36 | { 37 | AddUsers(filePath, adminUsername, adminPassword, registrationPassword); 38 | } 39 | else 40 | { 41 | Log.Error("Invalid arguments. Usage: WordPressAutoKit.exe add-user --file-path --admin-username --admin-password --registration-password "); 42 | } 43 | } 44 | else 45 | { 46 | Log.Error("Invalid arguments. Usage: WordPressAutoKit.exe add-user --file-path --admin-username --admin-password --registration-password "); 47 | } 48 | } 49 | else 50 | { 51 | DisplayMenu(); 52 | } 53 | } 54 | catch (Exception ex) 55 | { 56 | Log.Fatal(ex, "An unhandled exception occurred."); 57 | } 58 | finally 59 | { 60 | Log.CloseAndFlush(); 61 | } 62 | } 63 | 64 | private static string ConvertToAbbreviation(string input) 65 | { 66 | if (string.IsNullOrWhiteSpace(input)) 67 | return string.Empty; 68 | 69 | string abbreviation = string.Empty; 70 | 71 | foreach (char c in input) 72 | { 73 | // Check if the character is uppercase 74 | if (char.IsUpper(c)) 75 | { 76 | abbreviation += char.ToLower(c); // Add the lowercase version of the uppercase letter 77 | } 78 | } 79 | 80 | // Ensure the first letter of the input is always included (handle the lowercase start of camelCase) 81 | if (abbreviation.Length == 0 || char.IsLower(input[0])) 82 | { 83 | abbreviation = input[0] + abbreviation; 84 | } 85 | 86 | return abbreviation; 87 | } 88 | private static string ConvertToHyphenSeparated(string input) 89 | { 90 | if (string.IsNullOrWhiteSpace(input)) 91 | return string.Empty; 92 | 93 | // Use Regex to insert hyphens before uppercase letters 94 | string result = Regex.Replace(input, @"([a-z])([A-Z])", "$1-$2"); 95 | 96 | // Convert the entire result to lowercase 97 | return result.ToLower(); 98 | } 99 | 100 | /// 101 | /// Displays help information for the a silent command. 102 | /// 103 | private static void DisplayHelp(string xmlMemberName, string silentMethod) 104 | { 105 | //var assemblyPath =Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 106 | //var assemblyName = Assembly.GetExecutingAssembly().GetName().Name; 107 | var xmlPath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, ".xml"); 108 | var xmlDoc = XDocument.Load(xmlPath); 109 | var member = xmlDoc.Descendants("member") 110 | .FirstOrDefault(m => m.Attribute("name")?.Value == xmlMemberName); 111 | 112 | if (member != null) 113 | { 114 | var summary = member.Element("summary")?.Value.Trim(); 115 | var parameters = member.Elements("param"); 116 | var examples = member.Elements("example"); 117 | 118 | Console.WriteLine("Description:"); 119 | Console.WriteLine($" {summary}"); 120 | Console.WriteLine(); 121 | Console.WriteLine("Usage:"); 122 | Console.WriteLine($" {Assembly.GetExecutingAssembly().GetName().Name}.exe {silentMethod} [options]"); 123 | Console.WriteLine(); 124 | Console.WriteLine("Options:"); 125 | 126 | foreach (var param in parameters) 127 | { 128 | var name = param.Attribute("name")?.Value; 129 | var description = param.Value.Trim(); 130 | Console.WriteLine($" --{ConvertToHyphenSeparated(name)}, -{ConvertToAbbreviation(name)} <{name}> {description}"); 131 | } 132 | Console.WriteLine(" --help, -h Displays this help message."); 133 | Console.WriteLine(); 134 | Console.WriteLine("Examples:"); 135 | foreach (var example in examples) 136 | { 137 | var description = example.Value.Trim(); 138 | Console.WriteLine($" {Assembly.GetExecutingAssembly().GetName().Name}.exe {description}"); 139 | } 140 | } 141 | else 142 | { 143 | Console.WriteLine("Help information not found."); 144 | } 145 | } 146 | private static void ConfigureLogging() 147 | { 148 | string logFilePath = $"logs/log_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; 149 | var minimumLevel = configurationService?.GetConfigValue("Serilog:MinimumLevel") ?? "Debug"; 150 | 151 | Log.Logger = new LoggerConfiguration() 152 | .MinimumLevel.Is(Enum.Parse(minimumLevel, true)) 153 | .WriteTo.Console() 154 | .WriteTo.File(logFilePath) 155 | .CreateLogger(); 156 | } 157 | 158 | private static string GetArgumentValue(string[] args, string longForm, string shortForm) 159 | { 160 | for (int i = 0; i < args.Length; i++) 161 | { 162 | if (args[i].Equals(longForm, StringComparison.OrdinalIgnoreCase) || args[i].Equals(shortForm, StringComparison.OrdinalIgnoreCase)) 163 | { 164 | if (i + 1 < args.Length) 165 | { 166 | return args[i + 1]; 167 | } 168 | } 169 | } 170 | return string.Empty; 171 | } 172 | 173 | private static void DisplayMenu() 174 | { 175 | Console.WriteLine(CoreUtilities.CreateWelcomeMessage("WordPress Automation Toolkit")); 176 | Console.WriteLine("Please select an option:"); 177 | Console.WriteLine("1. Add Users"); 178 | Console.WriteLine("2. Remove Users"); 179 | Console.WriteLine("3. Update Users"); 180 | Console.WriteLine("4. Exit"); 181 | 182 | int option = GetOptionFromUser(); 183 | 184 | switch (option) 185 | { 186 | case 1: 187 | AddUsers(); 188 | DisplayMenu(); 189 | break; 190 | case 2: 191 | RemoveUsers(); 192 | DisplayMenu(); 193 | break; 194 | case 3: 195 | UpdateUsers(); 196 | DisplayMenu(); 197 | break; 198 | case 4: 199 | Log.Information("Exiting the App..."); 200 | break; 201 | default: 202 | Log.Warning("Invalid option. Please try again."); 203 | DisplayMenu(); 204 | break; 205 | } 206 | } 207 | 208 | private static int GetOptionFromUser() 209 | { 210 | Console.WriteLine("Enter the option number: "); 211 | string input = Console.ReadLine() ?? string.Empty; 212 | if (int.TryParse(input, out int option)) 213 | { 214 | string optionText = option switch 215 | { 216 | 1 => "Add Users", 217 | 2 => "Remove Users", 218 | 3 => "Update Users", 219 | 4 => "Exit", 220 | _ => "Invalid option" 221 | }; 222 | Log.Information($"User selected option {option}: {optionText}"); 223 | return option; 224 | } 225 | else 226 | { 227 | Log.Warning("Invalid option. Please try again."); 228 | return GetOptionFromUser(); 229 | } 230 | } 231 | 232 | private static void AddUsers() 233 | { 234 | string filePath = fileInteractionService != null && filePathService != null 235 | ? fileInteractionService.GetFilePathWithExtension(filePathService) 236 | : string.Empty; 237 | if (string.IsNullOrEmpty(filePath)) 238 | { 239 | Log.Error("File path is empty. Exiting Add Users process."); 240 | return; 241 | } 242 | Log.Information($"File path selected: {filePath}"); 243 | 244 | string adminUsername = GetAdminUsername(); 245 | if (string.IsNullOrEmpty(adminUsername)) 246 | { 247 | Log.Error("Admin username is empty. Exiting Add Users process."); 248 | return; 249 | } 250 | 251 | string adminPassword = GetAdminPassword(); 252 | if (string.IsNullOrEmpty(adminPassword)) 253 | { 254 | Log.Error("Admin password is empty. Exiting Add Users process."); 255 | return; 256 | } 257 | 258 | string registrationPassword = GetRegistrationPassword(); 259 | if (string.IsNullOrEmpty(registrationPassword)) 260 | { 261 | Log.Error("Registration password is empty. Exiting Add Users process."); 262 | return; 263 | } 264 | 265 | AddUsers(filePath, adminUsername, adminPassword, registrationPassword); 266 | } 267 | /// 268 | /// The add-user command is used to add new users to the system from a specified file. This command allows you to specify the file path containing user data, admin credentials for login, and the registration password to be used for the new users. 269 | /// 270 | /// The path to the file containing user data. 271 | /// The admin username for login. 272 | /// The admin password for login. 273 | /// The password to use for registration. 274 | /// add-user --file-path users.xlsx --admin-username admin --admin-password admin123 --registration-password reg123 275 | /// add-user -fp users.xlsx -au admin -ap admin123 -rp reg123 276 | 277 | private static void AddUsers(string filePath, string adminUsername, string adminPassword, string registrationPassword) 278 | { 279 | using (var driver = webDriverService?.CreateWebDriver()) 280 | { 281 | if (driver == null) 282 | { 283 | Log.Error("Failed to create WebDriver. Exiting the program."); 284 | return; 285 | } 286 | 287 | try 288 | { 289 | AdminLogin(driver, adminUsername, adminPassword); 290 | AddUsers(driver, filePath, registrationPassword); 291 | } 292 | catch (Exception ex) 293 | { 294 | Log.Error(ex, "An error occurred while adding users."); 295 | } 296 | finally 297 | { 298 | webDriverService?.QuitWebDriver(driver); 299 | } 300 | } 301 | } 302 | 303 | private static void AdminLogin(IWebDriver driver, string adminUsername, string adminPassword) 304 | { 305 | string loginUrl = configurationService?.GetConfigValue("PlatformSettings:LoginUrl") ?? string.Empty; 306 | adminLoginService?.Login(driver, loginUrl, adminUsername, adminPassword); 307 | } 308 | 309 | private static void AddUsers(IWebDriver driver, string filePath, string registrationPassword) 310 | { 311 | Log.Information("Starting to add users from Excel file"); 312 | 313 | string addNewUserUrl = configurationService?.GetConfigValue("PlatformSettings:AddNewUserUrl") ?? string.Empty; 314 | var columnNames = configurationService?.GetColumnNames("DataColumns:ColumnNames") ?? new List(); 315 | var dataList = excelReaderService?.ReadData(filePath, columnNames) ?? new List>(); 316 | var role = configurationService?.GetConfigValue("PlatformSettings:PostRegisterRole") ?? string.Empty; 317 | 318 | Log.Information($"Read {dataList.Count} entries from Excel file"); 319 | 320 | foreach (var data in dataList) 321 | { 322 | var userData = new UserData(data["Username"], data["Email"], data["Membership"]); 323 | Log.Information($"Adding user: {userData.UserName}, Email: {userData.Email}, Membership: {userData.Membership}"); 324 | 325 | userManagementService?.AddNewUser(driver, userData, registrationPassword, addNewUserUrl); 326 | Log.Information($"User {userData.UserName} added successfully"); 327 | 328 | roleService?.UpdateRole(driver, role); 329 | Log.Information($"Role updated to {role} for user {userData.UserName}"); 330 | 331 | membershipService?.AddMembership(driver, data["Membership"]); 332 | Log.Information($"Membership {data["Membership"]} added for user {userData.UserName}"); 333 | } 334 | 335 | Log.Information("Completed adding users from Excel file"); 336 | } 337 | 338 | private static string GetAdminUsername() 339 | { 340 | string adminUserNames = configurationService?.GetConfigValue("PlatformSettings:AdminUserNames") ?? string.Empty; 341 | string adminUsername = authenticationService?.GetAdminUsername(adminUserNames) ?? string.Empty; 342 | Log.Information($"Choosing to log as {adminUsername} Admin"); 343 | return adminUsername; 344 | } 345 | 346 | private static string GetAdminPassword() 347 | { 348 | string adminPassword = passwordService?.PromptForPassword("Enter your admin password:") ?? string.Empty; 349 | Log.Information("Adding Admin Password"); 350 | if (string.IsNullOrEmpty(adminPassword)) 351 | { 352 | Log.Error("Invalid password. Exiting the program."); 353 | } 354 | return adminPassword; 355 | } 356 | 357 | private static string GetRegistrationPassword() 358 | { 359 | string registrationPassword = passwordService?.PromptForPassword("Enter the password to use for registration:") ?? string.Empty; 360 | Log.Information("Adding Registration Password"); 361 | if (string.IsNullOrEmpty(registrationPassword)) 362 | { 363 | Log.Error("Invalid registration password. Exiting the program."); 364 | } 365 | return registrationPassword; 366 | } 367 | 368 | private static void RemoveUsers() 369 | { 370 | Log.Information("Remove Users method is not implemented yet."); 371 | } 372 | 373 | private static void UpdateUsers() 374 | { 375 | Log.Information("Update Users method is not implemented yet."); 376 | } 377 | 378 | private static void InitializeServices() 379 | { 380 | configurationService = new ConfigurationService(); 381 | filePathService = new FilePathService(); 382 | authenticationService = new AuthenticationService(); 383 | passwordService = new PasswordService(); 384 | webDriverService = new WebDriverService(); 385 | excelReaderService = new ExcelReaderService(); 386 | userManagementService = new UserManagementService(); 387 | roleService = new RoleService(); 388 | membershipService = new MembershipService(); 389 | adminLoginService = new AdminLoginService(); 390 | fileInteractionService = new FileInteractionService(); 391 | } 392 | 393 | private static IConfigurationService? configurationService; 394 | private static IFilePathService? filePathService; 395 | private static IAuthenticationService? authenticationService; 396 | private static IPasswordService? passwordService; 397 | private static IWebDriverService? webDriverService; 398 | private static IExcelReaderService? excelReaderService; 399 | private static IUserManagementService? userManagementService; 400 | private static IRoleService? roleService; 401 | private static IMembershipService? membershipService; 402 | private static IAdminLoginService? adminLoginService; 403 | private static IFileInteractionService? fileInteractionService; 404 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Dovs.WordPressAutoKit": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "APP_ENVIRONMENT": "$(Configuration)" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/AdminLoginService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | using OpenQA.Selenium; 3 | using Dovs.WordPressAutoKit.Common; 4 | 5 | namespace Dovs.WordPressAutoKit.Services 6 | { 7 | /// 8 | /// Service for handling admin login operations. 9 | /// 10 | public class AdminLoginService : IAdminLoginService 11 | { 12 | /// 13 | /// Logs in to the admin panel using the provided WebDriver, username, and password. 14 | /// 15 | /// The WebDriver instance used to perform the login. 16 | /// The admin username. 17 | /// The admin password. 18 | public void Login(IWebDriver driver, string loginUrl, string username, string password) 19 | { 20 | driver.Navigate().GoToUrl(loginUrl); 21 | FillLoginForm(driver, username, password); 22 | ClickLoginButton(driver); 23 | System.Threading.Thread.Sleep(1000); 24 | } 25 | 26 | /// 27 | /// Fills the login form with the provided username and password. 28 | /// 29 | /// The WebDriver instance used to fill the form. 30 | /// The admin username. 31 | /// The admin password. 32 | private void FillLoginForm(IWebDriver driver, string username, string password) 33 | { 34 | driver.FindElement(By.Id(ElementIds.USER_NAME_INPUT)).SendKeys(username); 35 | driver.FindElement(By.Id(ElementIds.USER_PASS_INPUT)).SendKeys(password); 36 | } 37 | 38 | /// 39 | /// Clicks the login button to submit the login form. 40 | /// 41 | /// The WebDriver instance used to click the button. 42 | private void ClickLoginButton(IWebDriver driver) 43 | { 44 | driver.FindElement(By.Id(ElementIds.LOGIN_BUTTON)).Click(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/AuthenticationService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | 3 | namespace Dovs.WordPressAutoKit.Services 4 | { 5 | /// 6 | /// Service for handling authentication operations. 7 | /// 8 | public class AuthenticationService : IAuthenticationService 9 | { 10 | /// 11 | /// Gets the admin username for login. 12 | /// 13 | /// Comma-separated admin usernames. 14 | /// The admin username. 15 | public string GetAdminUsername(string adminUserNames) 16 | { 17 | var usernames = adminUserNames.Split(','); 18 | 19 | if (usernames.Length < 2) 20 | { 21 | throw new ArgumentException("Please provide at least two admin usernames separated by a comma."); 22 | } 23 | 24 | Console.WriteLine("Choose a username to login as admin:"); 25 | for (int i = 0; i < usernames.Length; i++) 26 | { 27 | Console.WriteLine($"{i + 1}. {usernames[i].Trim()}"); 28 | } 29 | Console.WriteLine($"{usernames.Length + 1}. Other"); 30 | 31 | string choice = Console.ReadLine(); 32 | int choiceNumber; 33 | if (int.TryParse(choice, out choiceNumber)) 34 | { 35 | if (choiceNumber >= 1 && choiceNumber <= usernames.Length) 36 | { 37 | return usernames[choiceNumber - 1].Trim(); 38 | } 39 | else if (choiceNumber == usernames.Length + 1) 40 | { 41 | Console.WriteLine("Enter your username:"); 42 | return Console.ReadLine(); 43 | } 44 | } 45 | 46 | return null; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/MembershipService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Support.UI; 4 | using Dovs.WordPressAutoKit.Common; 5 | 6 | namespace Dovs.WordPressAutoKit.Services 7 | { 8 | /// 9 | /// Service for handling membership level updates. 10 | /// 11 | public class MembershipService : IMembershipService 12 | { 13 | /// 14 | /// Updates the membership level using the specified web driver. 15 | /// 16 | /// The web driver used to interact with the web page. 17 | /// The membership level to select. 18 | public void AddMembership(IWebDriver driver, string membershipLevel) 19 | { 20 | // Step 1: Click on the first link with class 'button button-secondary' to redirect to the membership edit page 21 | var addMembershipFirstButton = driver.FindElement(By.CssSelector(ElementIds.ADD_MEMBERSHIP_FIRST_BUTTON)); 22 | addMembershipFirstButton.Click(); 23 | 24 | // Step 2: Click on the second with class 'button-secondary pmpro-has-icon pmpro-has-icon-plus pmpro-member-change-level' 25 | var addMembershipSecondButton = driver.FindElement(By.CssSelector(ElementIds.ADD_MEMBERSHIP_SECOND_BUTTON)); 26 | addMembershipSecondButton.Click(); 27 | 28 | // Step 3: Choose the membership from the select dropdown by ID 29 | var membershipDropDown = driver.FindElement(By.Id(ElementIds.MEMBERSHIP_LEVEL_DROP)); 30 | var selectElement = new SelectElement(membershipDropDown); 31 | selectElement.SelectByText(membershipLevel); // Choose the membership level 32 | 33 | // Step 4: Click on the button with class 'button button-primary' to save the changes 34 | var saveButton = driver.FindElement(By.CssSelector(ElementIds.ADD_MEMBERSHIP_SAVE_BUTTON)); 35 | saveButton.Click(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/PasswordService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | 3 | 4 | namespace Dovs.WordPressAutoKit.Services 5 | { 6 | /// 7 | /// Service for handling password input operations. 8 | /// 9 | public class PasswordService : IPasswordService 10 | { 11 | /// 12 | /// Prompts the user for a password with the specified prompt message. 13 | /// 14 | /// The prompt message to display to the user. 15 | /// The password entered by the user. 16 | public string PromptForPassword(string prompt) 17 | { 18 | Console.WriteLine(prompt); 19 | return ReadPassword(); 20 | } 21 | 22 | /// 23 | /// Reads the password input from the user, masking the input with asterisks. 24 | /// 25 | /// The password entered by the user. 26 | private string ReadPassword() 27 | { 28 | var password = new System.Text.StringBuilder(); 29 | ConsoleKeyInfo key; 30 | 31 | while ((key = Console.ReadKey(intercept: true)).Key != ConsoleKey.Enter) 32 | { 33 | if (key.Key == ConsoleKey.Backspace && password.Length > 0) 34 | { 35 | password.Length--; 36 | Console.Write("\b \b"); 37 | } 38 | else if (key.Key != ConsoleKey.Backspace) 39 | { 40 | password.Append(key.KeyChar); 41 | Console.Write("*"); 42 | } 43 | } 44 | 45 | Console.WriteLine(); 46 | return password.ToString(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/RoleService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Common; 2 | using Dovs.WordPressAutoKit.Interfaces; 3 | using OpenQA.Selenium.Support.UI; 4 | using OpenQA.Selenium; 5 | 6 | namespace Dovs.WordPressAutoKit.Services 7 | { 8 | public class RoleService : IRoleService 9 | { 10 | /// 11 | /// Updates the user role. 12 | /// 13 | /// The web driver used to interact with the web page. 14 | /// The role to update to. 15 | public void UpdateRole(IWebDriver driver, string role) 16 | { 17 | SelectRole(driver, role); 18 | SaveChanges(driver); 19 | } 20 | 21 | /// 22 | /// Selects the user role from the dropdown. 23 | /// 24 | /// The web driver used to interact with the web page. 25 | /// The role to select. 26 | private static void SelectRole(IWebDriver driver, string role) 27 | { 28 | var roleDropdown = driver.FindElement(By.Id(ElementIds.ROLE_SELECTION)); 29 | var selectElement = new SelectElement(roleDropdown); 30 | selectElement.SelectByValue(role); 31 | } 32 | 33 | /// 34 | /// Saves the changes made to the user. 35 | /// 36 | /// The web driver used to interact with the web page. 37 | private static void SaveChanges(IWebDriver driver) 38 | { 39 | driver.FindElement(By.Id(ElementIds.UPDATE_USER_BUTTON)).Click(); 40 | System.Threading.Thread.Sleep(1000); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/UserManagementService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | using OpenQA.Selenium; 3 | using Dovs.WordPressAutoKit.Common; 4 | 5 | namespace Dovs.WordPressAutoKit.Services 6 | { 7 | /// 8 | /// Service for managing user operations. 9 | /// 10 | public class UserManagementService : IUserManagementService 11 | { 12 | /// 13 | /// Adds a new user with the specified details. 14 | /// 15 | /// The web driver used to interact with the web page. 16 | /// The user data containing user details. 17 | /// The password for the new user. 18 | public void AddNewUser(IWebDriver driver, UserData userData, string password, string addNewUserUrl) 19 | { 20 | NavigateToAddNewUserPage(driver, addNewUserUrl); 21 | FillUserDetails(driver, userData, password); 22 | SubmitAddingForm(driver); 23 | NavigateToUrl(driver, GetConfirmationUrl(driver)); 24 | } 25 | 26 | /// 27 | /// Navigates to the add new user page. 28 | /// 29 | /// The web driver used to interact with the web page. 30 | private void NavigateToAddNewUserPage(IWebDriver driver, string addNewUserUrl) 31 | { 32 | driver.Navigate().GoToUrl(addNewUserUrl); 33 | } 34 | 35 | /// 36 | /// Fills the user details in the form. 37 | /// 38 | /// The web driver used to interact with the web page. 39 | /// The user data containing user details. 40 | /// The password for the new user. 41 | private void FillUserDetails(IWebDriver driver, UserData userData, string password) 42 | { 43 | FillBasicDetails(driver, userData); 44 | FillPassword(driver, password); 45 | } 46 | 47 | /// 48 | /// Fills the basic user details in the form. 49 | /// 50 | /// The web driver used to interact with the web page. 51 | /// The user data containing user details. 52 | private static void FillBasicDetails(IWebDriver driver, UserData userData) 53 | { 54 | driver.FindElement(By.Id(ElementIds.USER_NAME_INPUT)).SendKeys(userData.UserName); 55 | driver.FindElement(By.Id(ElementIds.EMAIL_INPUT)).SendKeys(userData.Email); 56 | 57 | string[] nameParts = userData.UserName.Split(new char[] { ' ' }, 2); 58 | string firstName = nameParts[0]; 59 | string lastName = nameParts.Length > 1 ? nameParts[1] : ""; 60 | 61 | driver.FindElement(By.Id(ElementIds.FIRST_NAME_INPUT)).SendKeys(firstName); 62 | driver.FindElement(By.Id(ElementIds.LAST_NAME_INPUT)).SendKeys(lastName); 63 | } 64 | 65 | /// 66 | /// Fills the password in the form. 67 | /// 68 | /// The web driver used to interact with the web page. 69 | /// The password for the new user. 70 | private static void FillPassword(IWebDriver driver, string password) 71 | { 72 | var passwordField = driver.FindElement(By.Id(ElementIds.NEW_USER_PASSWORD_INPUT)); 73 | passwordField.Clear(); 74 | passwordField.SendKeys(password); 75 | } 76 | 77 | /// 78 | /// Submits the add new user form. 79 | /// 80 | /// The web driver used to interact with the web page. 81 | private static void SubmitAddingForm(IWebDriver driver) 82 | { 83 | driver.FindElement(By.Id(ElementIds.CREATE_USER_BUTTON)).Click(); 84 | System.Threading.Thread.Sleep(1000); 85 | } 86 | 87 | /// 88 | /// Gets the confirmation URL after adding a new user. 89 | /// 90 | /// The web driver used to interact with the web page. 91 | /// The confirmation URL. 92 | private static string GetConfirmationUrl(IWebDriver driver) 93 | { 94 | var messageDiv = driver.FindElement(By.Id(ElementIds.CONFIRMATION_DIV)); 95 | var anchorTag = messageDiv.FindElement(By.TagName(ElementIds.ANCHOR_TAG_NAME)); 96 | return anchorTag.GetAttribute(ElementIds.ANCHOR_TAG_ATTRIBUTE); 97 | } 98 | 99 | /// 100 | /// Navigates to the specified URL. 101 | /// 102 | /// The web driver used to interact with the web page. 103 | /// The URL to navigate to. 104 | private static void NavigateToUrl(IWebDriver driver, string url) 105 | { 106 | driver.Navigate().GoToUrl(url); 107 | System.Threading.Thread.Sleep(1000); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/Services/WebDriverService.cs: -------------------------------------------------------------------------------- 1 | using Dovs.WordPressAutoKit.Interfaces; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Chrome; 4 | 5 | namespace Dovs.WordPressAutoKit.Services 6 | { 7 | /// 8 | /// Service for managing WebDriver instances. 9 | /// 10 | public class WebDriverService : IWebDriverService 11 | { 12 | /// 13 | /// Creates a new instance of the Chrome WebDriver. 14 | /// 15 | /// A new instance of . 16 | public IWebDriver CreateWebDriver() 17 | { 18 | return new ChromeDriver(); 19 | } 20 | 21 | /// 22 | /// Quits the specified WebDriver instance. 23 | /// 24 | /// The WebDriver instance to quit. 25 | public void QuitWebDriver(IWebDriver driver) 26 | { 27 | driver.Quit(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/UserData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Dovs.WordPressAutoKit 8 | { 9 | /// 10 | /// Represents user data including username, email, and membership level. 11 | /// 12 | public class UserData 13 | { 14 | /// 15 | /// Gets the username of the user. 16 | /// 17 | public string UserName { get; } 18 | 19 | /// 20 | /// Gets the email of the user. 21 | /// 22 | public string Email { get; } 23 | 24 | /// 25 | /// Gets the membership level of the user. 26 | /// 27 | public string Membership { get; } 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The username of the user. 33 | /// The email of the user. 34 | /// The membership level of the user. 35 | public UserData(string userName, string email, string membership) 36 | { 37 | UserName = userName; 38 | Email = email; 39 | Membership = membership; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/appsettings.Release.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": "Information" 4 | }, 5 | "PlatformSettings": { 6 | "LoginUrl": "https://example.com/admin", 7 | "AddNewUserUrl": "https://example.com/wp-admin/user-new.php", 8 | "AdminUserNames": "Admin1,Admin2", 9 | "PreRegisterRole": "pre_register", 10 | "PostRegisterRole": "member" 11 | }, 12 | "DataColumns": { 13 | "UserNameColumn": "Username", 14 | "EmailColumn": "Email", 15 | "MembershipColumn": "Membership", 16 | "ColumnNames": "Username,Email,Membership" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/Dovs.WordPressAutoKit/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": "Information" 4 | }, 5 | "PlatformSettings": { 6 | "LoginUrl": "https://example.com/admin", 7 | "AddNewUserUrl": "https://example.com/wp-admin/user-new.php", 8 | "AdminUserNames": "Admin1,Admin2", 9 | "PreRegisterRole": "pre_register", 10 | "PostRegisterRole": "member" 11 | }, 12 | "DataColumns": { 13 | "UserNameColumn": "Username", 14 | "EmailColumn": "Email", 15 | "MembershipColumn": "Membership", 16 | "ColumnNames": "Username,Email,Membership" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/iac/environments/pre-prod/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | workload = var.workload 3 | environment = var.environment 4 | location = var.location 5 | instance = var.instance 6 | 7 | base_name = "${local.workload}-${local.environment}-${local.location}-${local.instance}" 8 | 9 | resource_types = ["rg", "vnet", "subnet", "nsg", "pip", "nic", "vm"] 10 | 11 | resource_names = { 12 | for resource in local.resource_types : 13 | resource => "${resource}-${local.base_name}" 14 | } 15 | } -------------------------------------------------------------------------------- /src/iac/environments/pre-prod/main.tf: -------------------------------------------------------------------------------- 1 | module "resource_group" { 2 | source = "../../modules/resource-group" 3 | resource_group_name = local.resource_names["rg"] 4 | location = var.location 5 | } 6 | 7 | module "network" { 8 | source = "../../modules/network" 9 | resource_group_name = local.resource_names["rg"] 10 | location = var.location 11 | vnet_name = local.resource_names["vnet"] 12 | subnet_name = local.resource_names["subnet"] 13 | 14 | depends_on = [module.resource_group] 15 | } 16 | 17 | module "security" { 18 | source = "../../modules/security" 19 | nsg_name = local.resource_names["nsg"] 20 | location = var.location 21 | resource_group_name = local.resource_names["rg"] 22 | security_rules = [ 23 | { 24 | name = "SSH" 25 | priority = 1001 26 | direction = "Inbound" 27 | access = "Allow" 28 | protocol = "Tcp" 29 | source_port_range = "*" 30 | destination_port_range = "22" 31 | source_address_prefix = "*" 32 | destination_address_prefix = "*" 33 | }, 34 | { 35 | name = "RDP" 36 | priority = 1002 37 | direction = "Inbound" 38 | access = "Allow" 39 | protocol = "Tcp" 40 | source_port_range = "*" 41 | destination_port_range = "3389" 42 | source_address_prefix = "*" 43 | destination_address_prefix = "*" 44 | }, 45 | { 46 | name = "HTTP" 47 | priority = 1003 48 | direction = "Inbound" 49 | access = "Allow" 50 | protocol = "Tcp" 51 | source_port_range = "*" 52 | destination_port_range = "80" 53 | source_address_prefix = "*" 54 | destination_address_prefix = "*" 55 | } 56 | ] 57 | 58 | depends_on = [module.resource_group] 59 | } 60 | 61 | module "compute" { 62 | source = "../../modules/compute" 63 | pip_name = local.resource_names["pip"] 64 | nic_name = local.resource_names["nic"] 65 | vm_name = local.resource_names["vm"] 66 | location = var.location 67 | resource_group_name = local.resource_names["rg"] 68 | subnet_id = module.network.subnet_id 69 | admin_username = var.admin_username 70 | admin_password = var.admin_password 71 | provisioner_script = var.provisioner_script 72 | 73 | depends_on = [module.resource_group] 74 | } 75 | 76 | resource "azurerm_network_interface_security_group_association" "nsg_association" { 77 | network_interface_id = module.compute.nic_id 78 | network_security_group_id = module.security.nsg_id 79 | } -------------------------------------------------------------------------------- /src/iac/environments/pre-prod/providers.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } -------------------------------------------------------------------------------- /src/iac/environments/pre-prod/variables.tf: -------------------------------------------------------------------------------- 1 | variable "workload" { 2 | description = "The name of the workload/application" 3 | type = string 4 | } 5 | 6 | variable "environment" { 7 | description = "The environment of the deployment" 8 | type = string 9 | } 10 | 11 | variable "location" { 12 | description = "The location of the resources" 13 | type = string 14 | } 15 | 16 | variable "instance" { 17 | description = "The instance number or identifier" 18 | type = string 19 | } 20 | 21 | variable "admin_username" { 22 | description = "The admin username for the virtual machine" 23 | type = string 24 | } 25 | 26 | variable "admin_password" { 27 | description = "The admin password for the virtual machine" 28 | type = string 29 | } 30 | 31 | variable "provisioner_script" { 32 | description = "The path to the script that will be used to provision configurations on the virtual machine" 33 | type = string 34 | } 35 | -------------------------------------------------------------------------------- /src/iac/modules/compute/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_public_ip" "pip" { 2 | name = var.pip_name 3 | location = var.location 4 | resource_group_name = var.resource_group_name 5 | allocation_method = "Static" 6 | } 7 | 8 | resource "azurerm_network_interface" "nic" { 9 | name = var.nic_name 10 | location = var.location 11 | resource_group_name = var.resource_group_name 12 | 13 | ip_configuration { 14 | name = "ipconfig1" 15 | subnet_id = var.subnet_id 16 | private_ip_address_allocation = "Dynamic" 17 | public_ip_address_id = azurerm_public_ip.pip.id 18 | } 19 | } 20 | 21 | resource "azurerm_virtual_machine" "vm" { 22 | name = var.vm_name 23 | location = var.location 24 | resource_group_name = var.resource_group_name 25 | network_interface_ids = [azurerm_network_interface.nic.id] 26 | vm_size = "Standard_DS1_v2" 27 | 28 | storage_os_disk { 29 | name = "osdisk" 30 | caching = "ReadWrite" 31 | create_option = "FromImage" 32 | managed_disk_type = "Standard_LRS" 33 | } 34 | 35 | storage_image_reference { 36 | publisher = "Canonical" 37 | offer = "0001-com-ubuntu-server-jammy" 38 | sku = "22_04-lts" 39 | version = "latest" 40 | } 41 | 42 | os_profile { 43 | computer_name = var.vm_name 44 | admin_username = var.admin_username 45 | admin_password = var.admin_password 46 | } 47 | 48 | os_profile_linux_config { 49 | disable_password_authentication = false 50 | } 51 | 52 | provisioner "remote-exec" { 53 | connection { 54 | type = "ssh" 55 | user = var.admin_username 56 | password = var.admin_password 57 | host = azurerm_public_ip.pip.ip_address 58 | } 59 | 60 | script = var.provisioner_script 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/iac/modules/compute/outputs.tf: -------------------------------------------------------------------------------- 1 | output "public_ip" { 2 | value = azurerm_public_ip.pip.ip_address 3 | } 4 | 5 | output "nic_id" { 6 | value = azurerm_network_interface.nic.id 7 | } 8 | 9 | output "vm_id" { 10 | value = azurerm_virtual_machine.vm.id 11 | } 12 | -------------------------------------------------------------------------------- /src/iac/modules/compute/variables.tf: -------------------------------------------------------------------------------- 1 | variable "pip_name" { 2 | description = "The name of the public IP address resource" 3 | type = string 4 | } 5 | 6 | variable "nic_name" { 7 | description = "The name of the network interface card (NIC) resource" 8 | type = string 9 | } 10 | 11 | variable "vm_name" { 12 | description = "The name of the virtual machine resource" 13 | type = string 14 | } 15 | 16 | variable "location" { 17 | description = "The Azure region where the resources will be created" 18 | type = string 19 | } 20 | 21 | variable "resource_group_name" { 22 | description = "The name of the resource group in which to create the resources" 23 | type = string 24 | } 25 | 26 | variable "subnet_id" { 27 | description = "The ID of the subnet in which to create the resources" 28 | type = string 29 | } 30 | 31 | variable "admin_username" { 32 | description = "The admin username for the virtual machine" 33 | type = string 34 | } 35 | 36 | variable "admin_password" { 37 | description = "The admin password for the virtual machine" 38 | type = string 39 | } 40 | 41 | variable "provisioner_script" { 42 | description = "The path to the script that will be used to provision configurations on the virtual machine" 43 | type = string 44 | } -------------------------------------------------------------------------------- /src/iac/modules/network/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_virtual_network" "vnet" { 2 | name = var.vnet_name 3 | address_space = ["10.0.0.0/16"] 4 | location = var.location 5 | resource_group_name = var.resource_group_name 6 | } 7 | 8 | resource "azurerm_subnet" "subnet" { 9 | name = var.subnet_name 10 | resource_group_name = var.resource_group_name 11 | virtual_network_name = azurerm_virtual_network.vnet.name 12 | address_prefixes = ["10.0.1.0/24"] 13 | } -------------------------------------------------------------------------------- /src/iac/modules/network/outputs.tf: -------------------------------------------------------------------------------- 1 | output "subnet_id" { 2 | value = azurerm_subnet.subnet.id 3 | } 4 | 5 | output "vnet_name" { 6 | value = azurerm_virtual_network.vnet.name 7 | } -------------------------------------------------------------------------------- /src/iac/modules/network/variables.tf: -------------------------------------------------------------------------------- 1 | variable "resource_group_name" { 2 | description = "The name of the resource group in which to create the network resources" 3 | type = string 4 | } 5 | 6 | variable "location" { 7 | description = "The Azure region where the network resources will be created" 8 | type = string 9 | } 10 | 11 | variable "vnet_name" { 12 | description = "The name of the virtual network (VNet) resource" 13 | type = string 14 | } 15 | 16 | variable "subnet_name" { 17 | description = "The name of the subnet within the virtual network" 18 | type = string 19 | } -------------------------------------------------------------------------------- /src/iac/modules/resource-group/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_resource_group" "rg" { 2 | name = var.resource_group_name 3 | location = var.location 4 | } -------------------------------------------------------------------------------- /src/iac/modules/resource-group/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevOpsVisions/wordpress-automation-toolkit/431a5b5e67be4ba11be517b163f4c0eef73dc04e/src/iac/modules/resource-group/outputs.tf -------------------------------------------------------------------------------- /src/iac/modules/resource-group/varibales.tf: -------------------------------------------------------------------------------- 1 | variable "resource_group_name" { 2 | description = "The name of the resource group in which to create the network resources" 3 | type = string 4 | } 5 | 6 | variable "location" { 7 | description = "The Azure region where the network resources will be created" 8 | type = string 9 | } -------------------------------------------------------------------------------- /src/iac/modules/security/main.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_network_security_group" "nsg" { 2 | name = var.nsg_name 3 | location = var.location 4 | resource_group_name = var.resource_group_name 5 | 6 | dynamic "security_rule" { 7 | for_each = var.security_rules 8 | content { 9 | name = security_rule.value.name 10 | priority = security_rule.value.priority 11 | direction = security_rule.value.direction 12 | access = security_rule.value.access 13 | protocol = security_rule.value.protocol 14 | source_port_range = security_rule.value.source_port_range 15 | destination_port_range = security_rule.value.destination_port_range 16 | source_address_prefix = security_rule.value.source_address_prefix 17 | destination_address_prefix = security_rule.value.destination_address_prefix 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/iac/modules/security/outputs.tf: -------------------------------------------------------------------------------- 1 | output "nsg_id" { 2 | value = azurerm_network_security_group.nsg.id 3 | } -------------------------------------------------------------------------------- /src/iac/modules/security/variables.tf: -------------------------------------------------------------------------------- 1 | variable "nsg_name" { 2 | description = "The name of the network security group (NSG) resource" 3 | type = string 4 | } 5 | 6 | variable "location" { 7 | description = "The Azure region where the network security group will be created" 8 | type = string 9 | } 10 | 11 | variable "resource_group_name" { 12 | description = "The name of the resource group in which to create the network security group" 13 | type = string 14 | } 15 | 16 | variable "security_rules" { 17 | description = "A list of security rules to apply to the network security group" 18 | type = list(object({ 19 | name = string 20 | priority = number 21 | direction = string 22 | access = string 23 | protocol = string 24 | source_port_range = string 25 | destination_port_range = string 26 | source_address_prefix = string 27 | destination_address_prefix = string 28 | })) 29 | } -------------------------------------------------------------------------------- /src/iac/scripts/install-lamp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Update and upgrade the system 4 | sudo apt update && sudo apt upgrade -y 5 | 6 | # Install prerequisites 7 | sudo apt install -y software-properties-common 8 | 9 | # Add the PHP 8.1 PPA 10 | sudo add-apt-repository ppa:ondrej/php -y 11 | sudo apt update 12 | 13 | # Install Apache 14 | sudo apt install -y apache2 15 | 16 | # Install PHP 8.1 and common modules 17 | sudo apt install -y php8.1 libapache2-mod-php8.1 php8.1-mysql 18 | 19 | # Install MySQL Server 20 | sudo apt install -y mysql-server 21 | 22 | # Restart Apache to apply PHP module 23 | sudo systemctl restart apache2 24 | 25 | # Verify installations 26 | apache2 -v 27 | mysql --version 28 | php -v 29 | --------------------------------------------------------------------------------