1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 20:10:08 +02:00

Compare commits

...

36 Commits
v5.5 ... v5.6.1

Author SHA1 Message Date
a407515534 🚀 Version 5.6.1 2022-09-24 12:56:14 +02:00
b827ffb869 🔧 Bump version number 2022-09-23 16:46:23 +02:00
bdb718598e 🐛 Various bugfixes
- Fixes issue with `scanApps` being non-optional in custom configuration
- Fixes issue with position of separator if Xdebug is not detected
- Ensure that `isRunningSwiftUIPreview` modifier always return false for debug builds
2022-09-23 16:46:13 +02:00
ddfc73e033 🐛 Resolve issue with determining PATH (#194)
In previous builds, PHP Monitor would use interactive mode when opening
a /bin/zsh shell, in order to be able to load the full PATH.

This is a problem because when launching PHP Monitor via the command
line or when you have some interactivity in an actual interactive
shell, it is possible to get stuck waiting for a particular input
which will effectively 'freeze' PHP Monitor's shell.

There are two options to fix this:
1) work with a timeout, which may or may not return a PATH
2) use a non-interactive shell and source .zshrc

I chose option 2, which is the more robust choice. If no .zshrc file
exists, it is also not sourced to avoid warnings or errors from ending
up in the PATH.
2022-09-23 16:44:06 +02:00
cfae520984 👌 Correctly resolve tagged version (#195) 2022-09-22 18:43:09 +02:00
0c176493e5 🔀 Merge branch 'main' into dev/5.6 2022-09-18 14:08:48 +02:00
71da62f954 📝 Update contribution guidelines 2022-09-18 14:08:10 +02:00
d6781568a3 📝 Update README 2022-09-18 13:39:40 +02:00
c9c7e14416 👌 Add support for <= and < version constraints
I am entirely unsure why one would need these, but I figured I'd get
these in the app before I start the work on PHP Monitor 6.0.

This ensures all common version constraints can now be parsed correctly.
2022-09-18 00:05:37 +02:00
0947dc5ecc 🚀 Version 5.6 2022-09-16 19:33:42 +02:00
286cdd00e9 🔧 Prepare for release 2022-09-16 19:31:32 +02:00
42b79d3cb3 🔧 Upgrade to Xcode 14 2022-09-10 21:44:17 +02:00
36aa41568c 🐛 Fix issue with minimum width w/ hidden UI 2022-09-10 21:43:26 +02:00
273c51f702 🔧 Update credits URLs 2022-09-09 21:41:20 +02:00
186f80c90e Allow more UI tweaking 2022-09-08 18:14:22 +02:00
d93af814c9 👌 Fix extension visibility of NSMenu 2022-09-08 17:21:41 +02:00
f4885f7dbc 👌 Update pre-release PHP version notice 2022-09-08 17:20:30 +02:00
805c9f5e6a Add new Preferences panel for UI tweaking 2022-09-07 21:40:43 +02:00
183d0bbc30 Allow hiding of global version switcher 2022-09-07 21:14:56 +02:00
4855c14d28 ♻️ Refactor Preferences & PreferenceName
Also added a few new preferences related to toggling specific menu items
based on your personal choices.

(These new settings still need to be added to the UI.)
2022-09-07 20:33:23 +02:00
588398ea76 🍱 New screenshots with updated UI 2022-09-06 18:02:27 +02:00
d2502cfba2 Improved site isolation switch (#191) 2022-09-05 21:09:31 +02:00
8d46fa8a4e 👌 Right-clicking a row selects it in domain list 2022-09-05 20:05:16 +02:00
e59b89ea49 🔧 Tweak URLs to reference phpmon.app domain 2022-09-05 19:55:41 +02:00
18b103bf9c 🚀 Version 5.5.1
This release fixes an issue that could occur if your username and your
home directory have different names. (#189)
2022-09-01 17:30:55 +02:00
9b8de47f5d Fix file membership so all tests pass again 2022-09-01 17:27:17 +02:00
da2934c2e5 Fix file membership so all tests pass again 2022-08-31 17:08:51 +02:00
9de4fc6712 🐛 Don't use whoami and use NSHomeDirectory()
- This commit replaces the usage of `whoami` with `id -un`.
- This also changes all `~` replacements with the result of calling
  the `NSHomeDirectory()` which may differ from `id -un` (#189)
2022-08-29 18:15:13 +02:00
4f4c950349 🐛 Don't use whoami and use NSHomeDirectory()
- This commit replaces the usage of `whoami` with `id -un`.
- This also changes all `~` replacements with the result of calling
  the `NSHomeDirectory()` which may differ from `id -un` (#189)
2022-08-29 18:06:11 +02:00
331ca8a9a4 🚀 Version 5.5 2022-08-28 15:24:28 +02:00
f4b1e0745a 🐛 Potential fix for parsing Valet version (#188) 2022-08-24 20:27:19 +02:00
c7bb4c1d37 ♻️ Refactor submenu creation 2022-08-22 19:10:36 +02:00
a17512bfad 📝 Update README 2022-08-22 18:48:43 +02:00
1292e91b33 ♻️ Cleanup 2022-08-18 17:38:46 +02:00
8c55fee18d ♻️ Refactor NSMenu structuring 2022-08-18 02:12:24 +02:00
663082d725 ♻️ Refactor NSMenu structuring
* Added a different way to create menus
* Added NoDomainResultsView (WIP)
2022-08-18 01:59:06 +02:00
46 changed files with 1073 additions and 633 deletions

23
.github/contributing.md vendored Normal file
View File

@ -0,0 +1,23 @@
# Contribution Guidelines
Thank you for your interest in contributing to PHP Monitor.
I consider this project a bit of a nice side-project to my daily gig, so it is very much a personal affair where I love to tinker around.
**While the code of the latest PHP Monitor release is public, many things are constantly in flux that may not be pushed to this repository yet.**
I don't mean to be rude, but I don't want other people involved with the project beyond simply contributing a few small things here and there, as has been the case in the past.
The extra mental overhead of having additional contributors to report to, whose code will need to be reviewed... it's a lot and it makes working on PHP Monitor less enjoyable for me.
Plus, at this point, the majority of PHP Monitor's main functionality is also done.
As a result, I may refer you to this file at some point. Again, I don't wish to be rude, but this general rule stands:
**Making any changes in a fork and opening a pull request without opening an issue first will most likely result in your PR being closed without mercy.**
To repeat, I am **not opposed** to small contributions and fixes, if they are **meaningful or insightful**.
To learn more, please check out the [pull request template](/.github/pull_request_template.md) which contains more information about my contribution requirements. (This will also show up when you open a new PR.)
Thank you for respecting this!

View File

@ -16,7 +16,7 @@ In short: It is usually best to *get in touch first* if you are making substanti
## About destination branches
Please keep in mind that `main` is reserved for the current code state of the latest release and should *never* be the destination branch unless a new release is happening. **Merge requests that target `main` will be closed without mercy.**
Please keep in mind that `main` is reserved for the current code state of the latest release and should *never* be the destination branch unless a new release is happening. **Pull requests that target `main` will be closed without mercy.**
Usually, the best target is the stable `dev/x.x` branch that corresponds with the latest major version that is released.

View File

@ -33,6 +33,8 @@
54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */; };
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; };
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */; };
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; };
C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; };
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */; };
@ -87,13 +89,10 @@
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; };
C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; };
C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; };
C422DDAD28A2DAC600CEAC97 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; };
C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; };
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; };
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42800A928452AA10099C999 /* StatusMenu+Items.swift */; };
C42800AB28452AA50099C999 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42800A928452AA10099C999 /* StatusMenu+Items.swift */; };
C4297F7A28970D59004C4630 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; };
C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; };
@ -131,6 +130,8 @@
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */; };
C44F868E2835BD8D005C353A /* phpmon-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C44F868D2835BD8D005C353A /* phpmon-config.json */; };
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; };
C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C450C8C528C919EC002A2B4B /* PreferenceName.swift */; };
C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; };
C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; };
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; };
@ -161,6 +162,23 @@
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; };
C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; };
C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; };
C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; };
C485706F28BF452300539B36 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; };
C485707028BF452300539B36 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; };
C485707128BF452E00539B36 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; };
C485707228BF453800539B36 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; };
C485707328BF454300539B36 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C485707428BF454E00539B36 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; };
C485707528BF454F00539B36 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; };
C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
C485707728BF455300539B36 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; };
C485707828BF456300539B36 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; };
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; };
C485707A28BF457800539B36 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; };
C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C485707D28BF45A200539B36 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
@ -173,6 +191,8 @@
C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; };
C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; };
C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; };
C4A81CA428C67101008DD9D1 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; };
C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A81CA328C67101008DD9D1 /* PMTableView.swift */; };
C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; };
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
@ -208,6 +228,8 @@
C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */; };
C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; };
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; };
C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; };
C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */; };
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */; };
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
@ -314,6 +336,8 @@
54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxPreferenceView.swift; sourceTree = "<group>"; };
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = "<group>"; };
54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotkeyPreferenceView.swift; sourceTree = "<group>"; };
C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDomainResultsView.swift; sourceTree = "<group>"; };
C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuItemExtension.swift; sourceTree = "<group>"; };
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
C4068CA327B0780A00544CD5 /* CheckboxPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CheckboxPreferenceView.xib; sourceTree = "<group>"; };
@ -354,7 +378,6 @@
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
C42337A2281F19F000459A48 /* Xdebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xdebug.swift; sourceTree = "<group>"; };
C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = "<group>"; };
C42800A928452AA10099C999 /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; };
C4297F7928970D59004C4630 /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = "<group>"; };
C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+FixMyValet.swift"; sourceTree = "<group>"; };
C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = "<group>"; };
@ -379,6 +402,7 @@
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = "<group>"; };
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = "<group>"; };
C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = "<group>"; };
C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = "<group>"; };
C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = "<group>"; };
C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = "<group>"; };
C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = "<group>"; };
@ -407,6 +431,7 @@
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = "<group>"; };
C4998F092617633900B2526E /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
C4A81CA328C67101008DD9D1 /* PMTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMTableView.swift; sourceTree = "<group>"; };
C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = "<group>"; };
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = "<group>"; };
@ -428,6 +453,7 @@
C4C0E8E627F88B41002D32A9 /* ProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyScanner.swift; sourceTree = "<group>"; };
C4C0E8E927F88B80002D32A9 /* ValetProxy+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetProxy+Fake.swift"; sourceTree = "<group>"; };
C4C1019A27C65C6F001FACC2 /* Process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = "<group>"; };
C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = "<group>"; };
C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Startup.swift"; sourceTree = "<group>"; };
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPrefs.swift; sourceTree = "<group>"; };
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
@ -497,6 +523,7 @@
C4998F092617633900B2526E /* PreferencesWindowController.swift */,
C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */,
5420395826135DC100FB00FA /* PrefsVC.swift */,
C450C8C528C919EC002A2B4B /* PreferenceName.swift */,
5420395E2613607600FB00FA /* Preferences.swift */,
C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */,
C4068CA927B0890D00544CD5 /* MenuBarIcons.swift */,
@ -814,6 +841,7 @@
C4FE011028084FC200D1DE6D /* SelectionVC.swift */,
C4930849279F331F009C240B /* AddSiteVC.swift */,
C4D9F24A280B69E100DCD39A /* AddProxyVC.swift */,
C4A81CA328C67101008DD9D1 /* PMTableView.swift */,
);
path = DomainList;
sourceTree = "<group>";
@ -828,7 +856,7 @@
C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */,
C4F361602836BFD9003598CC /* MainMenu+Actions.swift */,
C47331A1247093B7009A0597 /* StatusMenu.swift */,
C42800A928452AA10099C999 /* StatusMenu+Items.swift */,
C4C3643828AE4FCE00C0770E /* StatusMenu+Items.swift */,
);
path = Menu;
sourceTree = "<group>";
@ -944,6 +972,7 @@
isa = PBXGroup;
children = (
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */,
C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */,
);
path = Domains;
sourceTree = "<group>";
@ -1120,6 +1149,7 @@
C46FA23E246C358E00944F05 /* StringExtension.swift */,
C48D0C9225CC804200CC7490 /* XibLoadable.swift */,
C42759662627662800093CAE /* NSMenuExtension.swift */,
C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */,
C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */,
C4EB53E628553117006F9937 /* ArrayExtension.swift */,
);
@ -1176,7 +1206,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
LastUpgradeCheck = 1400;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C41C1B3222B0097F00E7CF16 = {
@ -1297,6 +1327,7 @@
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */,
C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */,
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */,
C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */,
@ -1319,8 +1350,8 @@
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */,
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */,
C4FACE80288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */,
C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */,
C415D3B72770F294005EF286 /* Actions.swift in Sources */,
C4C3643928AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */,
C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */,
C4CDA893288F1A71007CE25F /* Keys.swift in Sources */,
C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */,
@ -1331,7 +1362,6 @@
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */,
C422DDAD28A2DAC600CEAC97 /* WarningsWindowController.swift in Sources */,
C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */,
@ -1367,6 +1397,7 @@
C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */,
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */,
C44067FB27E25FD70045BD4E /* DomainListTLSCell.swift in Sources */,
C4A81CA428C67101008DD9D1 /* PMTableView.swift in Sources */,
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */,
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
@ -1375,12 +1406,14 @@
C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */,
C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */,
C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */,
C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */,
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C485707028BF452300539B36 /* WarningsWindowController.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */,
@ -1392,6 +1425,7 @@
C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */,
54D9E0BA27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */,
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */,
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */,
@ -1412,6 +1446,7 @@
files = (
C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */,
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
C485707128BF452E00539B36 /* WarningManager.swift in Sources */,
C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */,
C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */,
54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */,
@ -1427,18 +1462,23 @@
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */,
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */,
C485707528BF454F00539B36 /* StatsView.swift in Sources */,
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C485707328BF454300539B36 /* OnboardingView.swift in Sources */,
C485707728BF455300539B36 /* HeaderView.swift in Sources */,
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */,
C485707A28BF457800539B36 /* WarningListView.swift in Sources */,
C4C0E8E827F88B41002D32A9 /* ProxyScanner.swift in Sources */,
C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */,
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */,
C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
@ -1456,17 +1496,23 @@
C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */,
C4C0E8EB27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */,
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
C485707428BF454E00539B36 /* ServicesView.swift in Sources */,
C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */,
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */,
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */,
C450C8C728C919EC002A2B4B /* PreferenceName.swift in Sources */,
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */,
C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */,
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
C4C3643A28AE4FCE00C0770E /* StatusMenu+Items.swift in Sources */,
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
C4D936CB27E3EE4A00BD69FE /* DomainListCellProtocol.swift in Sources */,
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */,
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */,
C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */,
@ -1474,7 +1520,6 @@
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
C40C5C9D2846A40600E28255 /* Preset.swift in Sources */,
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
C42800AB28452AA50099C999 /* StatusMenu+Items.swift in Sources */,
C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */,
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */,
@ -1489,6 +1534,7 @@
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */,
C485707D28BF45A200539B36 /* WarningView.swift in Sources */,
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
C4CDA894288F1A71007CE25F /* Keys.swift in Sources */,
@ -1497,14 +1543,17 @@
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */,
C485707228BF453800539B36 /* SwiftUIHelper.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
C485707828BF456300539B36 /* Warning.swift in Sources */,
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */,
C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */,
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */,
C4F5FBCD28218CB8001065C5 /* Xdebug.swift in Sources */,
C40B24F227A310770018C7D2 /* Events.swift in Sources */,
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
@ -1515,10 +1564,12 @@
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */,
C485707628BF455100539B36 /* SectionHeaderView.swift in Sources */,
C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */,
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */,
C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */,
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
C485706F28BF452300539B36 /* WarningsWindowController.swift in Sources */,
C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */,
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
@ -1526,6 +1577,7 @@
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */,
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */,
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */,
C4A81CA528C67101008DD9D1 /* PMTableView.swift in Sources */,
C4B585422770FE3900DA4FBE /* Shell.swift in Sources */,
C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */,
C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */,
@ -1596,6 +1648,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -1658,6 +1711,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -1687,7 +1741,8 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 951;
CURRENT_PROJECT_VERSION = 973;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
@ -1697,7 +1752,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.5.0;
MARKETING_VERSION = 5.6.1;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1714,7 +1769,8 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 951;
CURRENT_PROJECT_VERSION = 973;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
@ -1724,7 +1780,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 5.5.0;
MARKETING_VERSION = 5.6.1;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1737,6 +1793,7 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
INFOPLIST_FILE = "phpmon-tests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
@ -1756,6 +1813,7 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
INFOPLIST_FILE = "phpmon-tests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,5 +1,5 @@
> **Note**
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider [sponsoring](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️
<p align="center"><img src="./docs/logo.png" alt="PHP Monitor Logo" width="500px" /></p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 519 KiB

View File

@ -18,4 +18,30 @@ class AppUpdaterCheckTest: XCTestCase {
XCTAssertNotNil(version)
}
func testTaggedReleaseOmitsZeroPatch() {
let version = AppVersion.from("3.5.0_333")!
XCTAssertEqual(version.tagged, "3.5")
XCTAssertEqual(version.version, "3.5.0")
}
func testTaggedReleaseDoesntOmitNonZeroPatch() {
let version = AppVersion.from("3.5.1_333")!
XCTAssertEqual(version.tagged, "3.5.1")
XCTAssertEqual(version.version, "3.5.1")
}
func testTagTruncationDoesntAffectMajorVersions() {
var version = AppVersion.from("5.0_333")!
XCTAssertEqual(version.tagged, "5.0")
XCTAssertEqual(version.version, "5.0")
version = AppVersion.from("5.0.0_333")!
XCTAssertEqual(version.tagged, "5.0")
XCTAssertEqual(version.version, "5.0.0")
}
}

View File

@ -8,6 +8,7 @@
import XCTest
// swiftlint:disable type_body_length
class PhpVersionNumberTest: XCTestCase {
func testCanDeconstructPhpVersion() throws {
@ -287,4 +288,76 @@ class PhpVersionNumberTest: XCTestCase {
.make(from: ["7.3.1", "7.2.9"]).all
)
}
func testCanCheckLessThanOrEqualConstraints() throws {
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<=7.2", strict: true),
PhpVersionNumberCollection
.make(from: ["7.2", "7.1", "7.0"]).all
)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<=7.2.0", strict: true),
PhpVersionNumberCollection
.make(from: ["7.2", "7.1", "7.0"]).all
)
// Strict check (>7.2.5 is too new for 7.2 which resolves to 7.2.0)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<=7.2.5", strict: true),
PhpVersionNumberCollection
.make(from: ["7.2", "7.1", "7.0"]).all
)
// Non-strict check (ignoring patch has no effect)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<=7.2.5", strict: false),
PhpVersionNumberCollection
.make(from: ["7.2", "7.1", "7.0"]).all
)
}
func testCanCheckLessThanConstraints() throws {
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<7.2", strict: true),
PhpVersionNumberCollection
.make(from: ["7.1", "7.0"]).all
)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<7.2.0", strict: true),
PhpVersionNumberCollection
.make(from: ["7.1", "7.0"]).all
)
// Strict check (>7.2.5 is too new for 7.2 which resolves to 7.2.0)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<7.2.5", strict: true),
PhpVersionNumberCollection
.make(from: ["7.2", "7.1", "7.0"]).all
)
// Non-strict check (patch resolves to 7.2.999, which is bigger than 7.2.5)
XCTAssertEqual(
PhpVersionNumberCollection
.make(from: ["7.4", "7.3", "7.2", "7.1", "7.0"])
.matching(constraint: "<7.2.5", strict: false),
PhpVersionNumberCollection
.make(from: ["7.1", "7.0"]).all
)
}
}

View File

@ -53,18 +53,22 @@ struct Constants {
struct Urls {
static let DonationPayment = URL(
string: "https://nicoverbruggen.be/sponsor#pay-now"
)!
// phpmon.app URLs (these are aliased to redirect correctly)
static let DonationPage = URL(
string: "https://nicoverbruggen.be/sponsor"
string: "https://phpmon.app/sponsor"
)!
static let FrequentlyAskedQuestions = URL(
string: "https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting"
string: "https://phpmon.app/faq"
)!
static let DonationPayment = URL(
string: "https://phpmon.app/sponsor/now"
)!
// GitHub URLs (do not alias these)
static let GitHubReleases = URL(
string: "https://github.com/nicoverbruggen/phpmon/releases"
)!

View File

@ -21,7 +21,7 @@ public class Paths {
init() {
baseDir = App.architecture != "x86_64" ? .opt : .usr
userName = String(Shell.pipe("whoami").split(separator: "\n")[0])
userName = String(Shell.pipe("id -un").split(separator: "\n")[0])
}
public func detectBinaryPaths() {
@ -57,6 +57,10 @@ public class Paths {
return shared.userName
}
public static var homePath: String {
return NSHomeDirectory()
}
public static var cellarPath: String {
return "\(shared.baseDir.rawValue)/Cellar"
}

View File

@ -14,12 +14,18 @@ extension Shell {
let task = Process()
task.launchPath = "/bin/zsh"
// We need an interactive shell so the user's PATH is loaded in correctly
task.arguments = ["--login", "-ilc", "echo $PATH"]
let command = Filesystem.fileExists("~/.zshrc")
// source the user's .zshrc file if it exists to complete $PATH
? ". ~/.zshrc && echo $PATH"
// otherwise, non-interactive mode is sufficient
: "echo $PATH"
task.arguments = ["--login", "-lc", command]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: String.Encoding.utf8) ?? ""

View File

@ -9,43 +9,17 @@
import Cocoa
extension NSMenu {
open func addItem(_ newItem: NSMenuItem, withKeyModifier modifier: NSEvent.ModifierFlags) {
newItem.keyEquivalentModifierMask = modifier
self.addItem(newItem)
convenience init(items: [NSMenuItem], target: NSObject? = nil) {
self.init()
self.addItems(items, target: target)
}
}
@IBDesignable class LocalizedMenuItem: NSMenuItem {
@IBInspectable
var localizationKey: String? {
didSet {
self.title = localizationKey?.localized ?? self.title
public func addItems(_ items: [NSMenuItem], target: NSObject? = nil) {
for item in items {
self.addItem(item)
if target != nil {
item.target = target
}
}
}
}
// MARK: - NSMenuItem subclasses
class PhpMenuItem: NSMenuItem {
var version: String = ""
}
class XdebugMenuItem: NSMenuItem {
var mode: String = ""
}
class ExtensionMenuItem: NSMenuItem {
var phpExtension: PhpExtension?
}
class EditorMenuItem: NSMenuItem {
var editor: Application?
}
class PresetMenuItem: NSMenuItem {
var preset: Preset?
}

View File

@ -0,0 +1,87 @@
//
// NSMenuItem.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 18/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Cocoa
extension NSMenuItem {
convenience init(
title: String,
action: Selector? = nil,
keyEquivalent: String = "",
keyModifier: NSEvent.ModifierFlags = [],
toolTip: String? = nil
) {
self.init(title: title, action: action, keyEquivalent: keyEquivalent)
self.keyEquivalentModifierMask = keyModifier
self.toolTip = toolTip
}
convenience init(
title: String,
keyEquivalent: String = "",
keyModifier: NSEvent.ModifierFlags = [],
toolTip: String? = nil,
submenu: [NSMenuItem],
target: NSObject? = nil
) {
self.init(title: title, action: nil, keyEquivalent: keyEquivalent)
self.keyEquivalentModifierMask = keyModifier
self.toolTip = toolTip
self.submenu = NSMenu(items: submenu, target: target)
}
}
// MARK: - NSMenuItem subclasses
@IBDesignable class LocalizedMenuItem: NSMenuItem {
@IBInspectable var localizationKey: String? {
didSet {
self.title = localizationKey?.localized ?? self.title
}
}
}
class PhpMenuItem: NSMenuItem {
var version: String = ""
}
class XdebugMenuItem: NSMenuItem {
var mode: String = ""
}
class ExtensionMenuItem: NSMenuItem {
var phpExtension: PhpExtension?
}
class EditorMenuItem: NSMenuItem {
var editor: Application?
}
class PresetMenuItem: NSMenuItem {
var preset: Preset?
static func getAll() -> [NSMenuItem] {
return Preferences.custom.presets!.map { preset in
let presetMenuItem = PresetMenuItem(
title: preset.getMenuItemText(),
action: #selector(MainMenu.togglePreset(sender:))
)
if let attributedString = try? NSMutableAttributedString(
data: preset.getMenuItemText().data(using: .utf8)!,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
) {
presetMenuItem.attributedTitle = attributedString
}
presetMenuItem.preset = preset
return presetMenuItem
}
}
}

View File

@ -16,7 +16,7 @@ class Filesystem {
*/
public static func exists(_ path: String) -> Bool {
return FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
atPath: path.replacingOccurrences(of: "~", with: Paths.homePath)
)
}
@ -26,13 +26,29 @@ class Filesystem {
public static func fileExists(_ path: String) -> Bool {
var isDirectory: ObjCBool = true
let exists = FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)"),
atPath: path.replacingOccurrences(of: "~", with: Paths.homePath),
isDirectory: &isDirectory
)
return exists && !isDirectory.boolValue
}
/**
Checks if a directory exists at the provided path.
*/
public static func directoryExists(_ path: String) -> Bool {
var isDirectory: ObjCBool = true
let exists = FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: Paths.homePath),
isDirectory: &isDirectory
)
return exists && isDirectory.boolValue
}
/**
Checks if a given file is a symbolic link.
*/
public static func fileIsSymlink(_ path: String) -> Bool {
do {
let attribs = try FileManager.default.attributesOfItem(atPath: path)
@ -42,17 +58,4 @@ class Filesystem {
}
}
/**
Checks if a directory exists at the provided path.
*/
public static func directoryExists(_ path: String) -> Bool {
var isDirectory: ObjCBool = true
let exists = FileManager.default.fileExists(
atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)"),
isDirectory: &isDirectory
)
return exists && isDirectory.boolValue
}
}

View File

@ -7,6 +7,7 @@
//
import Foundation
import Cocoa
class Xdebug {
@ -26,6 +27,26 @@ class Xdebug {
return value.components(separatedBy: ",").filter { self.modes.contains($0) }
}
public static func asMenuItems() -> [NSMenuItem] {
var items: [NSMenuItem] = []
let activeModes = Self.activeModes
for mode in Self.modes {
let item = XdebugMenuItem(
title: mode,
action: #selector(MainMenu.toggleXdebugMode(sender:)),
keyEquivalent: ""
)
item.state = activeModes.contains(mode) ? .on : .off
item.mode = mode
items.append(item)
}
return items
}
public static var modes: [String] {
return [
"develop",

View File

@ -17,10 +17,10 @@ class PhpHelper {
let dotless = version.replacingOccurrences(of: ".", with: "")
// Determine the dotless name for this PHP version
let destination = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)"
let destination = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
// Check if the ~/.config/phpmon/bin directory is in the PATH
let inPath = Shell.user.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin")
let inPath = Shell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin")
// Check if we can create symlinks (`/usr/local/bin` must be writable)
let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
@ -81,7 +81,7 @@ class PhpHelper {
}
private static func createSymlink(_ dotless: String) {
let source = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)"
let source = "\(Paths.homePath)/.config/phpmon/bin/pm\(dotless)"
let destination = "/usr/local/bin/pm\(dotless)"
if !Filesystem.fileExists(destination) {

View File

@ -87,6 +87,14 @@ public struct PhpVersionNumberCollection: Equatable {
return self.versions.filter { $0.isNewerThan(version, strict) }
}
if let version = PhpVersionNumber.make(from: constraint, type: .smallerThanOrEqual) {
return self.versions.filter { $0.isSameAs(version, strict) || $0.isOlderThan(version, strict)}
}
if let version = PhpVersionNumber.make(from: constraint, type: .smallerThan) {
return self.versions.filter { $0.isOlderThan(version, strict)}
}
return []
}
}
@ -116,12 +124,8 @@ public struct PhpVersionNumber: Equatable, Hashable {
case tildeVersionRange = #"^~(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case greaterThanOrEqual = #"^>=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case greaterThan = #"^>(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
// TODO: (6.0) Handle these cases (even though I suspect these are uncommon)
/*
case smallerThanOrEqual = #"^<=(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
case smallerThan = #"^<(?<major>\d+).(?<minor>\d+).?(?<patch>\d+)?\z"#
*/
}
public static func parse(_ text: String) throws -> Self {
@ -175,6 +179,15 @@ public struct PhpVersionNumber: Equatable, Hashable {
)
}
internal func isOlderThan(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
return (
self.major < version.major ||
self.major == version.major && self.minor < version.minor ||
self.major == version.major && self.minor == version.minor
&& self.patch(strict) < version.patch(strict)
)
}
internal func hasNewerMinorVersionOrPatch(_ version: PhpVersionNumber, _ strict: Bool) -> Bool {
return self.major == version.major &&
(

View File

@ -32,18 +32,11 @@ class PhpConfigurationFile: CreatedFromFile {
/** Resolves a PHP configuration file (.ini) */
static func from(filePath: String) -> Self? {
let path = filePath.replacingOccurrences(
of: "~",
with: "/Users/\(Paths.whoami)"
)
let path = filePath.replacingOccurrences(of: "~", with: Paths.homePath)
do {
let fileContents = try String(contentsOfFile: path)
return Self.init(
path: path,
contents: fileContents
)
return Self.init(path: path, contents: fileContents)
} catch {
Log.warn("Could not read the PHP configuration file at: `\(filePath)`")
return nil
@ -70,10 +63,8 @@ class PhpConfigurationFile: CreatedFromFile {
}
public func getConfig(for key: String) -> ConfigValue? {
for (_, section) in self.content {
if section.keys.contains(key) {
return section[key]!
}
for (_, section) in self.content where section.keys.contains(key) {
return section[key]!
}
return nil
}

View File

@ -13,9 +13,9 @@
</head>
<body>
<br>
<p><b>Do you enjoy using the app?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ</a> section, I did my best to ensure everything is documented.</p>
<p><b>Want to support further development of PHP Monitor?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
<p><b>Do you enjoy using the app?</b> Leave a <a href="https://phpmon.app/github">star on GitHub</a>!</p>
<p><b>Having issues?</b> Consult the <a href="https://phpmon.app/faq">FAQ</a> section, I did my best to ensure everything is documented.</p>
<p><b>Want to support further development of PHP Monitor?</b> You can <a href="https://phpmon.app/sponsor">financially support</a> the continued development of this app.</p>
<p><b>Get the latest on Twitter</b> Give me a <a href="https://twitter.com/nicoverbruggen">follow on Twitter</a> to learn about the latest and greatest updates of this app.</p>
<br>
</body>

View File

@ -146,8 +146,9 @@ class AppUpdateChecker {
text: "updater.alerts.buttons.release_notes".localized,
action: { vc in
vc.close(with: .OK)
NSWorkspace.shared.open(
Constants.Urls.GitHubReleases.appendingPathComponent("/tag/v\(version.version)\(devSuffix)")
Constants.Urls.GitHubReleases.appendingPathComponent("/tag/v\(version.tagged)\(devSuffix)")
)
}
)

View File

@ -66,6 +66,14 @@ class AppVersion {
return AppVersion.from("\(App.shortVersion)_\(App.bundleVersion)")!
}
var tagged: String {
if version.suffix(2) == ".0" && version.count > 3 {
return String(version.dropLast(2))
}
return version
}
var computerReadable: String {
return "\(version)_\(build ?? "0")"
}

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
@ -818,9 +819,9 @@ Gw
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="6IL-DW-37w">
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" multipleSelection="NO" autosaveName="phpmon-sitelist-columns" rowHeight="54" headerView="xUg-Mq-OSh" viewBased="YES" id="cp3-34-pQj">
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" multipleSelection="NO" autosaveName="phpmon-sitelist-columns" rowHeight="54" headerView="xUg-Mq-OSh" viewBased="YES" id="cp3-34-pQj" customClass="PMTableView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="626" height="281"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>

View File

@ -42,14 +42,15 @@ extension DomainListVC {
addDisabledIsolation(to: menu)
}
addUnlink(to: menu, with: site)
menu.addItem(HeaderView.asMenuItem(text: "domain_list.actions".localized))
addToggleSecure(to: menu, secured: site.secured)
addUnlink(to: menu, with: site)
tableView.menu = menu
}
private func addSystemApps(to menu: NSMenu) {
menu.addItem(withTitle: "domain_list.system_apps".localized, action: nil, keyEquivalent: "")
menu.addItem(HeaderView.asMenuItem(text: "domain_list.system_apps".localized))
menu.addItem(
withTitle: "domain_list.open_in_finder".localized,
action: #selector(self.openInFinder),
@ -70,7 +71,7 @@ extension DomainListVC {
private func addDetectedApps(to menu: NSMenu) {
if !applications.isEmpty {
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "domain_list.detected_apps".localized, action: nil, keyEquivalent: "")
menu.addItem(HeaderView.asMenuItem(text: "domain_list.detected_apps".localized))
for editor in applications {
let editorMenuItem = EditorMenuItem(
@ -96,38 +97,40 @@ extension DomainListVC {
}
private func addDisabledIsolation(to menu: NSMenu) {
menu.addItem(HeaderView.asMenuItem(text: "domain_list.site_isolation".localized))
menu.addItem(withTitle: "domain_list.isolation_unavailable".localized, action: nil, keyEquivalent: "")
menu.addItem(NSMenuItem.separator())
}
private func addIsolate(to menu: NSMenu, with site: ValetSite) {
if site.isolatedPhpVersion == nil {
// ISOLATION POSSIBLE
let isolationMenuItem = NSMenuItem(title: "domain_list.isolate".localized, action: nil, keyEquivalent: "")
let submenu = NSMenu()
submenu.addItem(withTitle: "Choose a PHP version", action: nil, keyEquivalent: "")
for version in PhpEnv.shared.availablePhpVersions.reversed() {
let item = PhpMenuItem(
title: "Always use PHP \(version)",
action: #selector(self.isolateSite),
keyEquivalent: ""
)
item.version = version
submenu.addItem(item)
}
menu.setSubmenu(submenu, for: isolationMenuItem)
var items: [NSMenuItem] = []
menu.addItem(isolationMenuItem)
menu.addItem(NSMenuItem.separator())
} else {
// REMOVE ISOLATION POSSIBLE
menu.addItem(
withTitle: "domain_list.remove_isolation".localized,
action: #selector(self.removeIsolatedSite),
for version in PhpEnv.shared.availablePhpVersions.reversed() {
let item = PhpMenuItem(
title: "domain_list.always_use_php".localized(version),
action: #selector(self.isolateSite),
keyEquivalent: ""
)
menu.addItem(NSMenuItem.separator())
if site.servingPhpVersion == version && site.isolatedPhpVersion != nil {
item.state = .on
item.action = nil
}
item.version = version
items.append(item)
}
// Add the option to remove site isolation
if site.isolatedPhpVersion != nil {
items.append(NSMenuItem.separator())
items.append(NSMenuItem(
title: "domain_list.remove_isolation".localized,
action: #selector(self.removeIsolatedSite)
))
}
menu.addItem(HeaderView.asMenuItem(text: "domain_list.site_isolation".localized))
menu.addItem(NSMenuItem(title: "domain_list.isolate".localized, submenu: items))
menu.addItem(NSMenuItem.separator())
}
private func addToggleSecure(to menu: NSMenu, secured: Bool) {

View File

@ -13,7 +13,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
// MARK: - Outlets
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var tableView: PMTableView!
@IBOutlet weak var progressIndicator: NSProgressIndicator!
// MARK: - Variables

View File

@ -0,0 +1,27 @@
//
// PMTableView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 05/09/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Cocoa
/**
This subclassed version of NSTableView selects a row upon right-clicking,
thus making the domain list behave more like you'd expect.
*/
public class PMTableView: NSTableView {
override open func menu(for event: NSEvent) -> NSMenu? {
let row = self.row(at: self.convert(event.locationInWindow, from: nil))
if row >= 0 {
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
}
return super.menu(for: event)
}
}

View File

@ -21,18 +21,12 @@ class NginxConfigurationFile: CreatedFromFile {
/** Resolves an nginx configuration file (.conf) */
static func from(filePath: String) -> Self? {
let path = filePath.replacingOccurrences(
of: "~",
with: "/Users/\(Paths.whoami)"
)
let path = filePath.replacingOccurrences(of: "~", with: Paths.homePath)
do {
let fileContents = try String(contentsOfFile: path)
return Self.init(
path: path,
contents: fileContents
)
return Self.init(path: path, contents: fileContents)
} catch {
Log.warn("Could not read the nginx configuration file at: `\(filePath)`")
return nil

View File

@ -20,7 +20,7 @@ class ValetSite: DomainListable {
/// replacing the user's home folder with ~.
lazy var absolutePathRelative: String = {
return self.absolutePath
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
.replacingOccurrences(of: Paths.homePath, with: "~")
}()
/// The TLD used to locate this site.

View File

@ -77,7 +77,11 @@ extension MainMenu {
ServicesManager.shared.loadData()
}
error == nil ? success() : failure(error!)
if error != nil {
return failure(error!)
}
success()
}
}
}

View File

@ -155,11 +155,11 @@ extension MainMenu {
App.shared.detectedApplications = Application.detectPresetApplications()
let customApps = Preferences.custom.scanApps.map { appName in
let customApps = Preferences.custom.scanApps?.map { appName in
return Application(appName, .user_supplied)
}.filter { app in
return app.isInstalled()
}
} ?? []
App.shared.detectedApplications.append(contentsOf: customApps)

View File

@ -51,25 +51,10 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
*/
private func rebuildMenu() {
let menu = StatusMenu()
menu.addPhpVersionMenuItems()
menu.addItem(NSMenuItem.separator())
menu.addPhpActionMenuItems()
menu.addItem(NSMenuItem.separator())
menu.addValetMenuItems()
menu.addItem(NSMenuItem.separator())
menu.addRemainingMenuItems()
menu.addItem(NSMenuItem.separator())
menu.addCoreMenuItems()
menu.addMenuItems()
menu.items.forEach({ (item) in
item.target = self
})
statusItem.menu = menu
statusItem.menu?.delegate = self
}

View File

@ -2,72 +2,176 @@
// StatusMenu+Items.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 30/05/2022.
// Created by Nico Verbruggen on 18/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Cocoa
// MARK: - PHP Version
extension StatusMenu {
// MARK: Remaining Menu Items
func addPhpVersionMenuItems() {
if PhpEnv.phpInstall.version.error {
let brokenMenuItems = ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"]
return addItems(brokenMenuItems.map { NSMenuItem(title: $0.localized) })
}
addItem(HeaderView.asMenuItem(
text: "\("mi_php_version".localized) \(PhpEnv.phpInstall.version.long)",
minimumWidth: 280 // this ensures the menu is at least wide enough not to cause clipping
))
}
func addPhpActionMenuItems() {
if PhpEnv.shared.isBusy {
addItem(NSMenuItem(title: "mi_busy".localized))
return
}
if PhpEnv.shared.availablePhpVersions.isEmpty { return }
addSwitchToPhpMenuItems()
self.addItem(NSMenuItem.separator())
}
func addServicesManagerMenuItem() {
if PhpEnv.shared.isBusy {
return
}
addItems([
ServicesView.asMenuItem(),
NSMenuItem.separator()
])
}
func addSwitchToPhpMenuItems() {
var shortcutKey = 1
for index in (0..<PhpEnv.shared.availablePhpVersions.count).reversed() {
// Get the short and long version
let shortVersion = PhpEnv.shared.availablePhpVersions[index]
let longVersion = PhpEnv.shared.cachedPhpInstallations[shortVersion]!.versionNumber
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
let versionString = long ? longVersion.toString() : shortVersion
let action = #selector(MainMenu.switchToPhpVersion(sender:))
let brew = (shortVersion == PhpEnv.brewPhpVersion) ? "php" : "php@\(shortVersion)"
let menuItem = PhpMenuItem(
title: "\("mi_php_switch".localized) \(versionString) (\(brew))",
action: (shortVersion == PhpEnv.phpInstall.version.short)
? nil
: action, keyEquivalent: "\(shortcutKey)"
)
menuItem.version = shortVersion
shortcutKey += 1
addItem(menuItem)
}
}
func addCoreMenuItems() {
addItems([
NSMenuItem.separator(),
NSMenuItem(title: "mi_preferences".localized,
action: #selector(MainMenu.openPrefs), keyEquivalent: ","),
NSMenuItem(title: "mi_check_for_updates".localized,
action: #selector(MainMenu.checkForUpdates)),
NSMenuItem.separator(),
NSMenuItem(title: "mi_about".localized,
action: #selector(MainMenu.openAbout)),
NSMenuItem(title: "mi_quit".localized,
action: #selector(MainMenu.terminateApp), keyEquivalent: "q")
])
}
// MARK: - Valet
func addValetMenuItems() {
addItems([
HeaderView.asMenuItem(text: "mi_valet".localized),
NSMenuItem(title: "mi_valet_config".localized,
action: #selector(MainMenu.openValetConfigFolder),
keyEquivalent: "v"),
NSMenuItem(title: "mi_domain_list".localized,
action: #selector(MainMenu.openDomainList),
keyEquivalent: "l"),
NSMenuItem.separator()
])
}
// MARK: - PHP Configuration
func addConfigurationMenuItems() {
self.addItem(HeaderView.asMenuItem(text: "mi_configuration".localized))
self.addItem(
addItems([
HeaderView.asMenuItem(text: "mi_configuration".localized),
NSMenuItem(title: "mi_php_config".localized,
action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c")
)
self.addItem(
action: #selector(MainMenu.openActiveConfigFolder),
keyEquivalent: "c"),
NSMenuItem(title: "mi_phpmon_config".localized,
action: #selector(MainMenu.openPhpMonitorConfigurationFile), keyEquivalent: "y")
)
self.addItem(
NSMenuItem(title: "mi_phpinfo".localized, action: #selector(MainMenu.openPhpInfo), keyEquivalent: "i")
)
action: #selector(MainMenu.openPhpMonitorConfigurationFile),
keyEquivalent: "y"),
NSMenuItem(title: "mi_phpinfo".localized,
action: #selector(MainMenu.openPhpInfo),
keyEquivalent: "i")
])
}
// MARK: - Composer
func addComposerMenuItems() {
self.addItem(HeaderView.asMenuItem(text: "mi_composer".localized))
self.addItem(
NSMenuItem(title: "mi_global_composer".localized,
action: #selector(MainMenu.openGlobalComposerFolder), keyEquivalent: "g")
)
let composerMenuItem = NSMenuItem(
title: "mi_update_global_composer".localized,
action: PhpEnv.shared.isBusy ? nil : #selector(MainMenu.updateGlobalComposerDependencies),
keyEquivalent: "g"
)
composerMenuItem.keyEquivalentModifierMask = .shift
self.addItem(composerMenuItem)
addItems([
HeaderView.asMenuItem(text: "mi_composer".localized),
NSMenuItem(
title: "mi_global_composer".localized,
action: #selector(MainMenu.openGlobalComposerFolder),
keyEquivalent: "g"
),
NSMenuItem(
title: "mi_update_global_composer".localized,
action: PhpEnv.shared.isBusy
? nil
: #selector(MainMenu.updateGlobalComposerDependencies),
keyEquivalent: "g",
keyModifier: [.shift]
)
])
}
// MARK: - Stats
func addStatsMenuItem() {
guard let stats = PhpEnv.phpInstall.limits else { return }
self.addItem(StatsView.asMenuItem(
addItem(StatsView.asMenuItem(
memory: stats.memory_limit,
post: stats.post_max_size,
upload: stats.upload_max_filesize)
)
}
// MARK: - Extensions
func addExtensionsMenuItems() {
self.addItem(HeaderView.asMenuItem(text: "mi_detected_extensions".localized))
addItem(HeaderView.asMenuItem(text: "mi_detected_extensions".localized))
if PhpEnv.phpInstall.extensions.isEmpty {
self.addItem(NSMenuItem(title: "mi_no_extensions_detected".localized, action: nil, keyEquivalent: ""))
addItem(NSMenuItem(title: "mi_no_extensions_detected".localized, action: nil, keyEquivalent: ""))
}
var shortcutKey = 1
for phpExtension in PhpEnv.phpInstall.extensions {
self.addExtensionItem(phpExtension, shortcutKey)
addExtensionItem(phpExtension, shortcutKey)
shortcutKey += 1
}
}
// MARK: - Presets
func addPresetsMenuItem() {
guard let presets = Preferences.custom.presets else {
addEmptyPresetHelp()
@ -82,178 +186,126 @@ extension StatusMenu {
addLoadedPresets()
}
func addEmptyPresetHelp() {
let presets = NSMenuItem(title: "mi_presets_title".localized, action: nil, keyEquivalent: "")
let presetsMenu = NSMenu()
presetsMenu.addItem(NSMenuItem(title: "mi_no_presets".localized, action: nil, keyEquivalent: ""))
presetsMenu.addItem(NSMenuItem.separator())
presetsMenu.addItem(NSMenuItem(
title: "mi_set_up_presets".localized,
action: #selector(MainMenu.showPresetHelp),
keyEquivalent: "")
)
presetsMenu.items.forEach { $0.target = MainMenu.shared }
self.setSubmenu(presetsMenu, for: presets)
self.addItem(presets)
return
private func addEmptyPresetHelp() {
addItem(NSMenuItem(title: "mi_presets_title".localized, submenu: [
NSMenuItem(title: "mi_no_presets".localized),
NSMenuItem.separator(),
NSMenuItem(title: "mi_set_up_presets".localized,
action: #selector(MainMenu.showPresetHelp))
], target: MainMenu.shared))
}
func addLoadedPresets() {
let presets = NSMenuItem(title: "mi_presets_title".localized, action: nil, keyEquivalent: "")
let presetsMenu = NSMenu()
presetsMenu.addItem(NSMenuItem.separator())
presetsMenu.addItem(HeaderView.asMenuItem(text: "mi_apply_presets_title".localized))
for preset in Preferences.custom.presets! {
let presetMenuItem = PresetMenuItem(
title: preset.getMenuItemText(),
action: #selector(MainMenu.togglePreset(sender:)),
keyEquivalent: ""
)
if let attributedString = try? NSMutableAttributedString(
data: preset.getMenuItemText().data(using: .utf8)!,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
) {
presetMenuItem.attributedTitle = attributedString
}
presetMenuItem.preset = preset
presetsMenu.addItem(presetMenuItem)
}
presetsMenu.addItem(NSMenuItem.separator())
presetsMenu.addItem(NSMenuItem(
title: "mi_revert_to_prev_config".localized,
action: PresetHelper.rollbackPreset != nil
? #selector(MainMenu.rollbackPreset)
: nil,
keyEquivalent: ""
))
presetsMenu.addItem(NSMenuItem.separator())
presetsMenu.addItem(NSMenuItem(
title: "mi_profiles_loaded".localized(
Preferences.custom.presets!.count
),
action: nil, keyEquivalent: "")
)
for item in presetsMenu.items {
item.target = MainMenu.shared
}
self.setSubmenu(presetsMenu, for: presets)
self.addItem(presets)
private func addLoadedPresets() {
addItem(NSMenuItem(title: "mi_presets_title".localized, submenu: [
NSMenuItem.separator(),
HeaderView.asMenuItem(text: "mi_apply_presets_title".localized)
] + PresetMenuItem.getAll() + [
NSMenuItem.separator(),
NSMenuItem(title: "mi_revert_to_prev_config".localized,
action: PresetHelper.rollbackPreset != nil ? #selector(MainMenu.rollbackPreset) : nil),
NSMenuItem.separator(),
NSMenuItem(title: "mi_profiles_loaded".localized(Preferences.custom.presets!.count))
], target: MainMenu.shared))
}
// MARK: - Xdebug
func addXdebugMenuItem() {
if !Xdebug.enabled {
return
}
self.addItem(NSMenuItem.separator())
let xdebugSwitch = NSMenuItem(
title: "mi_xdebug_mode".localized,
action: nil,
keyEquivalent: ""
)
let xdebugModesMenu = NSMenu()
let activeModes = Xdebug.activeModes
xdebugModesMenu.addItem(HeaderView.asMenuItem(text: "mi_xdebug_available_modes".localized))
for mode in Xdebug.modes {
let item = XdebugMenuItem(
title: mode,
action: #selector(MainMenu.toggleXdebugMode(sender:)),
keyEquivalent: ""
)
item.state = activeModes.contains(mode) ? .on : .off
item.mode = mode
xdebugModesMenu.addItem(item)
}
xdebugModesMenu.addItem(HeaderView.asMenuItem(text: "mi_xdebug_actions".localized))
xdebugModesMenu.addItem(
withTitle: "mi_xdebug_disable_all".localized,
action: #selector(MainMenu.disableAllXdebugModes),
keyEquivalent: ""
)
for item in xdebugModesMenu.items {
item.target = MainMenu.shared
}
self.setSubmenu(xdebugModesMenu, for: xdebugSwitch)
self.addItem(xdebugSwitch)
addItems([
NSMenuItem(title: "mi_xdebug_mode".localized, submenu: [
HeaderView.asMenuItem(text: "mi_xdebug_available_modes".localized)
] + Xdebug.asMenuItems() + [
HeaderView.asMenuItem(text: "mi_xdebug_actions".localized),
NSMenuItem(title: "mi_xdebug_disable_all".localized,
action: #selector(MainMenu.disableAllXdebugModes))
], target: MainMenu.shared),
NSMenuItem.separator()
], target: MainMenu.shared)
}
// MARK: - PHP Doctor
func addPhpDoctorMenuItem() {
if !Preferences.isEnabled(.showPhpDoctorSuggestions) ||
!WarningManager.shared.hasWarnings() {
return
}
addItems([
HeaderView.asMenuItem(text: "mi_php_doctor".localized),
NSMenuItem(title: "mi_recommendations_count".localized(WarningManager.shared.warnings.count)),
NSMenuItem(title: "mi_view_recommendations".localized, action: #selector(MainMenu.openWarnings)),
NSMenuItem.separator()
])
}
// MARK: - First Aid & Services
func addFirstAidAndServicesMenuItems() {
let services = NSMenuItem(title: "mi_other".localized, action: nil, keyEquivalent: "")
let services = NSMenuItem(title: "mi_other".localized)
let servicesMenu = NSMenu()
servicesMenu.addItems([
// FIRST AID
HeaderView.asMenuItem(text: "mi_first_aid".localized),
NSMenuItem(title: "mi_view_onboarding".localized, action: #selector(MainMenu.showWelcomeTour)),
NSMenuItem(title: "mi_fa_php_doctor".localized, action: #selector(MainMenu.openWarnings)),
NSMenuItem.separator(),
NSMenuItem(title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
action: #selector(MainMenu.fixMyValet),
toolTip: "mi_fix_my_valet_tooltip".localized),
NSMenuItem(title: "mi_fix_brew_permissions".localized(), action: #selector(MainMenu.fixHomebrewPermissions),
toolTip: "mi_fix_brew_permissions_tooltip".localized),
NSMenuItem.separator(),
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_first_aid".localized))
// SERVICES
HeaderView.asMenuItem(text: "mi_services".localized),
NSMenuItem(title: "mi_restart_dnsmasq".localized, action: #selector(MainMenu.restartDnsMasq),
keyEquivalent: "d"),
NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm),
keyEquivalent: "p"),
NSMenuItem(title: "mi_restart_nginx".localized, action: #selector(MainMenu.restartNginx),
keyEquivalent: "n"),
NSMenuItem(title: "mi_restart_valet_services".localized, action: #selector(MainMenu.restartValetServices),
keyEquivalent: "s"),
NSMenuItem(title: "mi_stop_valet_services".localized, action: #selector(MainMenu.stopValetServices),
keyEquivalent: "s",
keyModifier: [.command, .shift]),
NSMenuItem.separator(),
servicesMenu.addItem(NSMenuItem(title: "mi_view_onboarding".localized,
action: #selector(MainMenu.showWelcomeTour), keyEquivalent: ""))
servicesMenu.addItem(NSMenuItem(title: "mi_fa_php_doctor".localized,
action: #selector(MainMenu.openWarnings), keyEquivalent: ""))
servicesMenu.addItem(NSMenuItem.separator())
let fixMyValetMenuItem = NSMenuItem(
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
action: #selector(MainMenu.fixMyValet), keyEquivalent: ""
)
fixMyValetMenuItem.toolTip = "mi_fix_my_valet_tooltip".localized
servicesMenu.addItem(fixMyValetMenuItem)
let fixHomebrewMenuItem = NSMenuItem(
title: "mi_fix_brew_permissions".localized(),
action: #selector(MainMenu.fixHomebrewPermissions), keyEquivalent: ""
)
fixHomebrewMenuItem.toolTip = "mi_fix_brew_permissions_tooltip".localized
servicesMenu.addItem(fixHomebrewMenuItem)
servicesMenu.addItem(NSMenuItem.separator())
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized,
action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized,
action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_nginx".localized,
action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
servicesMenu.addItem(NSMenuItem(title: "mi_restart_valet_services".localized,
action: #selector(MainMenu.restartValetServices), keyEquivalent: "s"))
servicesMenu.addItem(
NSMenuItem(title: "mi_stop_valet_services".localized,
action: #selector(MainMenu.stopValetServices), keyEquivalent: "s"),
withKeyModifier: [.command, .shift]
)
servicesMenu.addItem(NSMenuItem.separator())
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_manual_actions".localized))
servicesMenu.addItem(
// MANUAL ACTIONS
HeaderView.asMenuItem(text: "mi_manual_actions".localized),
NSMenuItem(title: "mi_php_refresh".localized,
action: #selector(MainMenu.reloadPhpMonitorMenuInForeground), keyEquivalent: "r")
)
action: #selector(MainMenu.reloadPhpMonitorMenuInForeground),
keyEquivalent: "r")
], target: MainMenu.shared)
for item in servicesMenu.items {
item.target = MainMenu.shared
}
self.setSubmenu(servicesMenu, for: services)
self.addItem(services)
setSubmenu(servicesMenu, for: services)
addItem(services)
}
// MARK: - Other helper methods to generate menu items
func addExtensionItem(_ phpExtension: PhpExtension, _ shortcutKey: Int) {
let keyEquivalent = shortcutKey < 9 ? "\(shortcutKey)" : ""
let menuItem = ExtensionMenuItem(
title: "\(phpExtension.name) (\(phpExtension.fileNameOnly))",
action: #selector(MainMenu.toggleExtension),
keyEquivalent: keyEquivalent
)
if menuItem.keyEquivalent != "" {
menuItem.keyEquivalentModifierMask = [.option]
}
menuItem.state = phpExtension.enabled ? .on : .off
menuItem.phpExtension = phpExtension
addItem(menuItem)
}
}

View File

@ -8,154 +8,63 @@
import Cocoa
class StatusMenu: NSMenu {
func addMenuItems() {
addPhpVersionMenuItems()
addItem(NSMenuItem.separator())
func addPhpVersionMenuItems() {
if PhpEnv.phpInstall.version.error {
for message in ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"] {
addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
}
return
if Preferences.isEnabled(.displayGlobalVersionSwitcher) {
addPhpActionMenuItems()
addItem(NSMenuItem.separator())
}
let phpVersionText = "\("mi_php_version".localized) \(PhpEnv.phpInstall.version.long)"
addItem(HeaderView.asMenuItem(text: phpVersionText))
}
func addPhpActionMenuItems() {
if PhpEnv.shared.isBusy {
addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
return
if Preferences.isEnabled(.displayServicesManager) {
addServicesManagerMenuItem()
addItem(NSMenuItem.separator())
}
if PhpEnv.shared.availablePhpVersions.isEmpty {
return
if Preferences.isEnabled(.displayValetIntegration) {
addValetMenuItems()
addItem(NSMenuItem.separator())
}
self.addSwitchToPhpMenuItems()
self.addItem(NSMenuItem.separator())
if Preferences.isEnabled(.displayPhpConfigFinder) {
addConfigurationMenuItems()
addItem(NSMenuItem.separator())
}
self.addItem(ServicesView.asMenuItem())
self.addItem(NSMenuItem.separator())
}
func addValetMenuItems() {
self.addItem(HeaderView.asMenuItem(text: "mi_valet".localized))
self.addItem(NSMenuItem(
title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
self.addItem(NSMenuItem(
title: "mi_domain_list".localized, action: #selector(MainMenu.openDomainList), keyEquivalent: "l"))
self.addItem(NSMenuItem.separator())
}
func addRemainingMenuItems() {
self.addConfigurationMenuItems()
self.addItem(NSMenuItem.separator())
self.addComposerMenuItems()
if Preferences.isEnabled(.displayComposerToolkit) {
addComposerMenuItems()
addItem(NSMenuItem.separator())
}
if PhpEnv.shared.isBusy {
return
}
self.addItem(NSMenuItem.separator())
self.addStatsMenuItem()
self.addItem(NSMenuItem.separator())
self.addExtensionsMenuItems()
self.addXdebugMenuItem()
self.addPhpDoctorMenuItem()
self.addItem(NSMenuItem.separator())
self.addPresetsMenuItem()
self.addFirstAidAndServicesMenuItems()
}
func addPhpDoctorMenuItem() {
if !Preferences.isEnabled(.showPhpDoctorSuggestions) ||
!WarningManager.shared.hasWarnings() {
return
if Preferences.isEnabled(.displayLimitsWidget) {
addStatsMenuItem()
addItem(NSMenuItem.separator())
}
self.addItem(NSMenuItem.separator())
self.addItem(HeaderView.asMenuItem(text: "mi_php_doctor".localized))
self.addItem(NSMenuItem(
title: "mi_recommendations_count".localized(WarningManager.shared.warnings.count),
action: nil,
keyEquivalent: ""
))
self.addItem(NSMenuItem(
title: "mi_view_recommendations".localized,
action: #selector(MainMenu.openWarnings),
keyEquivalent: ""
))
}
if Preferences.isEnabled(.displayExtensions) {
addExtensionsMenuItems()
NSMenuItem.separator()
func addCoreMenuItems() {
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_preferences".localized,
action: #selector(MainMenu.openPrefs), keyEquivalent: ","))
self.addItem(NSMenuItem(title: "mi_check_for_updates".localized,
action: #selector(MainMenu.checkForUpdates), keyEquivalent: ""))
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_about".localized,
action: #selector(MainMenu.openAbout), keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_quit".localized,
action: #selector(MainMenu.terminateApp), keyEquivalent: "q"))
}
// MARK: Private Helpers
internal func addSwitchToPhpMenuItems() {
var shortcutKey = 1
for index in (0..<PhpEnv.shared.availablePhpVersions.count).reversed() {
// Get the short and long version
let shortVersion = PhpEnv.shared.availablePhpVersions[index]
let longVersion = PhpEnv.shared.cachedPhpInstallations[shortVersion]!.versionNumber
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
let versionString = long ? longVersion.toString() : shortVersion
let action = #selector(MainMenu.switchToPhpVersion(sender:))
let brew = (shortVersion == PhpEnv.brewPhpVersion) ? "php" : "php@\(shortVersion)"
let menuItem = PhpMenuItem(
title: "\("mi_php_switch".localized) \(versionString) (\(brew))",
action: (shortVersion == PhpEnv.phpInstall.version.short)
? nil
: action, keyEquivalent: "\(shortcutKey)"
)
menuItem.version = shortVersion
shortcutKey += 1
self.addItem(menuItem)
}
}
internal func addExtensionItem(_ phpExtension: PhpExtension, _ shortcutKey: Int) {
let keyEquivalent = shortcutKey < 9 ? "\(shortcutKey)" : ""
let menuItem = ExtensionMenuItem(
title: "\(phpExtension.name) (\(phpExtension.fileNameOnly))",
action: #selector(MainMenu.toggleExtension),
keyEquivalent: keyEquivalent
)
if menuItem.keyEquivalent != "" {
menuItem.keyEquivalentModifierMask = [.option]
addXdebugMenuItem()
}
menuItem.state = phpExtension.enabled ? .on : .off
menuItem.phpExtension = phpExtension
addPhpDoctorMenuItem()
self.addItem(menuItem)
if Preferences.isEnabled(.displayPresets) {
addPresetsMenuItem()
}
if Preferences.isEnabled(.displayMisc) {
addFirstAidAndServicesMenuItems()
}
addItem(NSMenuItem.separator())
addCoreMenuItems()
}
}

View File

@ -9,7 +9,7 @@
import Foundation
struct CustomPrefs: Decodable {
let scanApps: [String]
let scanApps: [String]?
let presets: [Preset]?
let services: [String]?
let environmentVariables: [String: String]?
@ -39,3 +39,57 @@ struct CustomPrefs: Decodable {
case environmentVariables = "export"
}
}
extension Preferences {
func loadCustomPreferences() {
// Ensure the configuration directory is created if missing
Shell.run("mkdir -p ~/.config/phpmon")
// Move the legacy file
moveOutdatedConfigurationFile()
// Attempt to load the file if it exists
let url = URL(fileURLWithPath: "\(Paths.homePath)/.config/phpmon/config.json")
if Filesystem.fileExists(url.path) {
Log.info("A custom ~/.config/phpmon/config.json file was found. Attempting to parse...")
loadCustomPreferencesFile(url)
} else {
Log.info("There was no /.config/phpmon/config.json file to be loaded.")
}
}
func moveOutdatedConfigurationFile() {
if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") {
Log.info("An outdated configuration file was found. Moving it...")
Shell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json")
Log.info("The configuration file was copied successfully!")
}
}
func loadCustomPreferencesFile(_ url: URL) {
do {
customPreferences = try JSONDecoder().decode(
CustomPrefs.self,
from: try! String(contentsOf: url, encoding: .utf8).data(using: .utf8)!
)
Log.info("The ~/.config/phpmon/config.json file was successfully parsed.")
if customPreferences.hasPresets() {
Log.info("There are \(customPreferences.presets!.count) custom presets.")
}
if customPreferences.hasServices() {
Log.info("There are custom services: \(customPreferences.services!)")
}
if customPreferences.hasEnvironmentVariables() {
Log.info("Configuring the additional exports...")
Shell.user.exports = customPreferences.getEnvironmentVariables()
}
} catch {
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
}
}
}

View File

@ -0,0 +1,106 @@
//
// PreferenceName.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 07/09/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
/**
These are the keys used for every preference in the app.
*/
enum PreferenceName: String {
// FIRST-TIME LAUNCH
case wasLaunchedBefore = "launched_before"
// GENERAL
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
case autoComposerGlobalUpdateAfterSwitch = "auto_composer_global_update_after_switch"
case allowProtocolForIntegrations = "allow_protocol_for_integrations"
case globalHotkey = "global_hotkey"
case automaticBackgroundUpdateCheck = "backgroundUpdateCheck"
case showPhpDoctorSuggestions = "show_php_doctor_suggestions"
// APPEARANCE
case shouldDisplayDynamicIcon = "use_dynamic_icon"
case iconTypeToDisplay = "icon_type_to_display"
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
// NOTIFICATIONS
case notifyAboutVersionChange = "notify_about_version_change"
case notifyAboutPhpFpmRestart = "notify_about_php_fpm_restart"
case notifyAboutServices = "notify_about_services_restart"
case notifyAboutPresets = "notify_about_presets"
case notifyAboutSecureToggle = "notify_about_secure_toggle"
case notifyAboutGlobalComposerStatus = "notify_about_composer_status"
// MENU CUSTOMIZATION
case displayGlobalVersionSwitcher = "display_global_version_switcher"
case displayServicesManager = "display_services_manager"
case displayValetIntegration = "display_valet_integration"
case displayPhpConfigFinder = "display_php_config_finder"
case displayComposerToolkit = "display_composer_toolkit"
case displayLimitsWidget = "display_limits_widget"
case displayExtensions = "display_extensions"
case displayPresets = "display_presets"
case displayMisc = "display_misc"
/**
What type of data each preference contains.
*/
static var mapping: [PreferenceType: [PreferenceName]] = [
.boolean: [
// Preferences
.shouldDisplayDynamicIcon,
.fullPhpVersionDynamicIcon,
.autoServiceRestartAfterExtensionToggle,
.autoComposerGlobalUpdateAfterSwitch,
.allowProtocolForIntegrations,
.automaticBackgroundUpdateCheck,
.showPhpDoctorSuggestions,
// Notifications
.notifyAboutVersionChange,
.notifyAboutPhpFpmRestart,
.notifyAboutServices,
.notifyAboutPresets,
.notifyAboutSecureToggle,
.notifyAboutGlobalComposerStatus,
// UI Preferences
.displayGlobalVersionSwitcher,
.displayServicesManager,
.displayValetIntegration,
.displayPhpConfigFinder,
.displayComposerToolkit,
.displayLimitsWidget,
.displayExtensions,
.displayPresets,
.displayMisc
],
.string: [
.globalHotkey,
.iconTypeToDisplay
]
]
}
enum PreferenceType {
case boolean, string
}
/**
These are retired preferences that, if present, should be migrated.
*/
enum RetiredPreferenceName: String {
case shouldDisplayPhpHintInIcon = "add_php_to_icon"
}
/**
These are internal stats. They NEVER get shared.
*/
enum InternalStats: String {
case launchCount = "times_launched"
case switchCount = "times_switched_versions"
case didSeeSponsorEncouragement = "did_see_sponsor_encouragement"
}

View File

@ -8,51 +8,6 @@
import Foundation
/**
These are the keys used for every preference in the app.
*/
enum PreferenceName: String {
// FIRST-TIME LAUNCH
case wasLaunchedBefore = "launched_before"
// GENERAL
case autoServiceRestartAfterExtensionToggle = "auto_restart_after_extension_toggle"
case autoComposerGlobalUpdateAfterSwitch = "auto_composer_global_update_after_switch"
case allowProtocolForIntegrations = "allow_protocol_for_integrations"
case globalHotkey = "global_hotkey"
case automaticBackgroundUpdateCheck = "backgroundUpdateCheck"
case showPhpDoctorSuggestions = "show_php_doctor_suggestions"
// APPEARANCE
case shouldDisplayDynamicIcon = "use_dynamic_icon"
case iconTypeToDisplay = "icon_type_to_display"
case fullPhpVersionDynamicIcon = "full_php_in_menu_bar"
// NOTIFICATIONS
case notifyAboutVersionChange = "notify_about_version_change"
case notifyAboutPhpFpmRestart = "notify_about_php_fpm_restart"
case notifyAboutServices = "notify_about_services_restart"
case notifyAboutPresets = "notify_about_presets"
case notifyAboutSecureToggle = "notify_about_secure_toggle"
case notifyAboutGlobalComposerStatus = "notify_about_composer_status"
}
/**
These are retired preferences that, if present, should be migrated.
*/
enum RetiredPreferenceName: String {
case shouldDisplayPhpHintInIcon = "add_php_to_icon"
}
/**
These are internal stats. They NEVER get shared.
*/
enum InternalStats: String {
case launchCount = "times_launched"
case switchCount = "times_switched_versions"
case didSeeSponsorEncouragement = "did_see_sponsor_encouragement"
}
class Preferences {
// MARK: - Singleton
@ -66,7 +21,12 @@ class Preferences {
public init() {
Preferences.handleFirstTimeLaunch()
cachedPreferences = Self.cache()
customPreferences = CustomPrefs(scanApps: [], presets: [], services: [], environmentVariables: [:])
customPreferences = CustomPrefs(
scanApps: [],
presets: [],
services: [],
environmentVariables: [:]
)
loadCustomPreferences()
}
@ -104,6 +64,17 @@ class Preferences {
PreferenceName.notifyAboutSecureToggle.rawValue: true,
PreferenceName.notifyAboutGlobalComposerStatus.rawValue: true,
/// Preferences: UI Preferences
PreferenceName.displayGlobalVersionSwitcher.rawValue: true,
PreferenceName.displayServicesManager.rawValue: true,
PreferenceName.displayValetIntegration.rawValue: true,
PreferenceName.displayPhpConfigFinder.rawValue: true,
PreferenceName.displayComposerToolkit.rawValue: true,
PreferenceName.displayLimitsWidget.rawValue: true,
PreferenceName.displayExtensions.rawValue: true,
PreferenceName.displayPresets.rawValue: true,
PreferenceName.displayMisc.rawValue: true,
/// Stats
InternalStats.switchCount.rawValue: 0,
InternalStats.launchCount.rawValue: 0,
@ -160,44 +131,18 @@ class Preferences {
// MARK: - Internal Functionality
private static func cache() -> [PreferenceName: Any] {
return [
// Part 1: Always Booleans
.shouldDisplayDynamicIcon:
UserDefaults.standard.bool(
forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue) as Any,
.fullPhpVersionDynamicIcon: UserDefaults.standard.bool(
forKey: PreferenceName.fullPhpVersionDynamicIcon.rawValue) as Any,
.autoServiceRestartAfterExtensionToggle: UserDefaults.standard.bool(
forKey: PreferenceName.autoServiceRestartAfterExtensionToggle.rawValue) as Any,
.autoComposerGlobalUpdateAfterSwitch: UserDefaults.standard.bool(
forKey: PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue) as Any,
.allowProtocolForIntegrations: UserDefaults.standard.bool(
forKey: PreferenceName.allowProtocolForIntegrations.rawValue) as Any,
.automaticBackgroundUpdateCheck: UserDefaults.standard.bool(
forKey: PreferenceName.automaticBackgroundUpdateCheck.rawValue) as Any,
.showPhpDoctorSuggestions: UserDefaults.standard.bool(
forKey: PreferenceName.showPhpDoctorSuggestions.rawValue) as Any,
.notifyAboutVersionChange: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutVersionChange.rawValue) as Any,
.notifyAboutPhpFpmRestart: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutPhpFpmRestart.rawValue) as Any,
.notifyAboutServices: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutServices.rawValue) as Any,
.notifyAboutPresets: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutPresets.rawValue) as Any,
.notifyAboutSecureToggle: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutSecureToggle.rawValue) as Any,
.notifyAboutGlobalComposerStatus: UserDefaults.standard.bool(
forKey: PreferenceName.notifyAboutGlobalComposerStatus.rawValue) as Any,
// Part 2: Always Strings
.globalHotkey: UserDefaults.standard.string(
forKey: PreferenceName.globalHotkey.rawValue) as Any,
.iconTypeToDisplay: UserDefaults.standard.string(
forKey: PreferenceName.iconTypeToDisplay.rawValue) as Any
]
private static func cache() -> [PreferenceName: Any?] {
return Dictionary(uniqueKeysWithValues: PreferenceName.mapping
.flatMap { (key: PreferenceType, value: [PreferenceName]) in
value.map { preference -> (PreferenceName, Any?) in
return (preference, { () -> Any? in
switch key {
case .boolean: return UserDefaults.standard.bool(forKey: preference.rawValue)
case .string: return UserDefaults.standard.string(forKey: preference.rawValue)
}
}())
}
})
}
static func update(_ preference: PreferenceName, value: Any?) {
@ -211,59 +156,4 @@ class Preferences {
// Update the preferences cache in memory!
Preferences.shared.cachedPreferences = Preferences.cache()
}
// MARK: - Custom Preferences
private func loadCustomPreferences() {
// Ensure the configuration directory is created if missing
Shell.run("mkdir -p ~/.config/phpmon")
// Move the legacy file
moveOutdatedConfigurationFile()
// Attempt to load the file if it exists
let url = URL(fileURLWithPath: "/Users/\(Paths.whoami)/.config/phpmon/config.json")
if Filesystem.fileExists(url.path) {
Log.info("A custom ~/.config/phpmon/config.json file was found. Attempting to parse...")
loadCustomPreferencesFile(url)
} else {
Log.info("There was no /.config/phpmon/config.json file to be loaded.")
}
}
private func moveOutdatedConfigurationFile() {
if Filesystem.fileExists("~/.phpmon.conf.json") && !Filesystem.fileExists("~/.config/phpmon/config.json") {
Log.info("An outdated configuration file was found. Moving it...")
Shell.run("cp ~/.phpmon.conf.json ~/.config/phpmon/config.json")
Log.info("The configuration file was copied successfully!")
}
}
private func loadCustomPreferencesFile(_ url: URL) {
do {
customPreferences = try JSONDecoder().decode(
CustomPrefs.self,
from: try! String(contentsOf: url, encoding: .utf8).data(using: .utf8)!
)
Log.info("The ~/.config/phpmon/config.json file was successfully parsed.")
if customPreferences.hasPresets() {
Log.info("There are \(customPreferences.presets!.count) custom presets.")
}
if customPreferences.hasServices() {
Log.info("There are custom services: \(customPreferences.services!)")
}
if customPreferences.hasEnvironmentVariables() {
Log.info("Configuring the additional exports...")
Shell.user.exports = customPreferences.getEnvironmentVariables()
}
} catch {
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
}
}
}

View File

@ -91,6 +91,11 @@ class PreferencesWindowController: PMWindowController {
label: "Appearance",
icon: "paintbrush"
),
PrefTabView(
viewController: MenuStructurePreferencesVC.fromStoryboard(),
label: "Visibility",
icon: "eye"
),
PrefTabView(
viewController: NotificationPreferencesVC.fromStoryboard(),
label: "Notifications",

View File

@ -188,6 +188,23 @@ class GenericPreferenceVC: NSViewController {
)
}
func getDisplayMenuSectionPV(
_ localizationKey: String,
_ preference: PreferenceName,
_ first: Bool = false
) -> NSView {
return CheckboxPreferenceView.make(
sectionText: first ? "prefs.menu_contents".localized : "",
descriptionText: "\(localizationKey)_desc".localized,
checkboxText: localizationKey.localized,
preference: preference,
action: {
MainMenu.shared.refreshIcon()
MainMenu.shared.rebuild()
}
)
}
// MARK: - Listening for hotkey delegate
var listeningForHotkeyView: HotkeyPreferenceView?
@ -240,9 +257,30 @@ class NotificationPreferencesVC: GenericPreferenceVC {
}
class AppearancePreferencesVC: GenericPreferenceVC {
class MenuStructurePreferencesVC: GenericPreferenceVC {
// MARK: - Lifecycle
public static func fromStoryboard() -> GenericPreferenceVC {
let vc = NSStoryboard(name: "Main", bundle: nil)
.instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC
vc.views = [
vc.getDisplayMenuSectionPV("prefs.display_global_version_switcher", .displayGlobalVersionSwitcher, true),
vc.getDisplayMenuSectionPV("prefs.display_services_manager", .displayServicesManager),
vc.getDisplayMenuSectionPV("prefs.display_valet_integration", .displayValetIntegration),
vc.getDisplayMenuSectionPV("prefs.display_php_config_finder", .displayPhpConfigFinder),
vc.getDisplayMenuSectionPV("prefs.display_composer_toolkit", .displayComposerToolkit),
vc.getDisplayMenuSectionPV("prefs.display_limits_widget", .displayLimitsWidget),
vc.getDisplayMenuSectionPV("prefs.display_extensions", .displayExtensions),
vc.getDisplayMenuSectionPV("prefs.display_presets", .displayPresets),
vc.getDisplayMenuSectionPV("prefs.display_misc", .displayMisc)
]
return vc
}
}
class AppearancePreferencesVC: GenericPreferenceVC {
public static func fromStoryboard() -> GenericPreferenceVC {
let vc = NSStoryboard(name: "Main", bundle: nil)

View File

@ -167,14 +167,15 @@ struct Preset: Codable, Equatable {
// MARK: - Menu Items
// swiftlint:disable void_function_in_ternary
public func getMenuItemText() -> String {
var info = extensions.count == 1
? "preset.extension".localized(extensions.count)
: "preset.extensions".localized(extensions.count)
? "preset.extension".localized(extensions.count)
: "preset.extensions".localized(extensions.count)
info += ", "
info += configuration.count == 1
? "preset.preference".localized(configuration.count)
: "preset.preferences".localized(configuration.count)
? "preset.preference".localized(configuration.count)
: "preset.preferences".localized(configuration.count)
if self.version == nil {
return "<span style=\"font-family: '-apple-system'; font-size: 12px;\">"
@ -263,7 +264,7 @@ struct Preset: Codable, Equatable {
try! String(data: data, encoding: .utf8)!
.write(
toFile: "/Users/\(Paths.whoami)/.config/phpmon/preset_revert.json",
toFile: "\(Paths.homePath)/.config/phpmon/preset_revert.json",
atomically: true,
encoding: .utf8
)

View File

@ -16,7 +16,7 @@ class PresetHelper {
public static func loadRollbackPresetFromFile() {
guard let revert = try? String(
contentsOfFile: "/Users/\(Paths.whoami)/.config/phpmon/preset_revert.json",
contentsOfFile: "\(Paths.homePath)/.config/phpmon/preset_revert.json",
encoding: .utf8
) else {
PresetHelper.rollbackPreset = nil

View File

@ -10,8 +10,13 @@ import Foundation
import SwiftUI
var isRunningSwiftUIPreview: Bool {
return ProcessInfo.processInfo
.environment["XCODE_RUNNING_FOR_PREVIEWS"] != nil
#if DEBUG
// If running SwiftUI *and* when debugging
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != nil
#else
// Release builds should always return false here
return false
#endif
}
extension Color {

View File

@ -0,0 +1,37 @@
//
// NoDomainResults.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 15/08/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct NoDomainResults: View {
@State var searching: Bool = false
var body: some View {
VStack(alignment: .center, spacing: 15) {
Image(systemName: searching ? "magnifyingglass.circle.fill" : "questionmark.circle.fill")
.resizable()
.renderingMode(.template)
.frame(width: 24, height: 24)
VStack(alignment: .center) {
Text(
searching
? "domain_list.no_domains_for_search_query".localizedForSwiftUI
: "domain_list.no_domains".localizedForSwiftUI
)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding(25)
}
}
struct NoDomainResults_Previews: PreviewProvider {
static var previews: some View {
NoDomainResults()
}
}

View File

@ -26,11 +26,17 @@ struct HeaderView: View {
static func asMenuItem(
text: String,
width: Int? = nil
minimumWidth: CGFloat? = nil
) -> NSMenuItem {
let view = NSHostingView(rootView: Self(text: text))
view.autoresizingMask = [.width, .height]
view.setFrameSize(CGSize(width: view.frame.width, height: 24))
var finalWidth = view.frame.width
if minimumWidth != nil && minimumWidth! > finalWidth {
finalWidth = minimumWidth!
}
view.setFrameSize(CGSize(width: finalWidth, height: 24))
let item = NSMenuItem()
item.view = view

View File

@ -19,7 +19,6 @@ struct NoWarningsView: View {
VStack(alignment: .center) {
Text("warnings.none".localizedForSwiftUI)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding(25)

View File

@ -32,7 +32,7 @@ class WarningManager {
),
Warning(
command: {
return !Shell.user.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin") &&
return !Shell.user.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") &&
!FileManager.default.isWritableFile(atPath: "/usr/local/bin/")
},
name: "Helpers cannot be symlinked and not in PATH",
@ -74,12 +74,10 @@ class WarningManager {
}
private func loopOverEvaluations() async {
for check in self.evaluations {
if await check.applies() {
Log.info("[DOCTOR] \(check.name) (!)")
self.warnings.append(check)
continue
}
for check in self.evaluations where await check.applies() {
Log.info("[DOCTOR] \(check.name) (!)")
self.warnings.append(check)
continue
}
}
}

View File

@ -100,6 +100,9 @@
"domain_list.title" = "Domains";
"domain_list.subtitle" = "";
"domain_list.no_domains" = "You have not set up any domains or proxies yet.";
"domain_list.no_domains_for_search_query" = "There are no results for your search query.";
"domain_list.tooltips.isolated" = "This domain is isolated and using PHP %@ instead of the globally linked PHP.";
"domain_list.tooltips.checkmark" = "This domain is being served with a version of PHP that is compatible with this requirement (PHP %@). Click on the PHP version next to this checkmark to find out more information about how this requirement was determined.";
@ -164,10 +167,13 @@
// SITE LIST ACTIONS
"domain_list.isolate" = "Isolate Domain";
"domain_list.isolate" = "Switch PHP Version";
"domain_list.site_isolation" = "Site Isolation";
"domain_list.remove_isolation" = "Remove Isolation";
"domain_list.always_use_php" = "Always use PHP %@";
"domain_list.isolation_unavailable" = "Isolation Not Supported (in Valet 2)";
"domain_list.actions" = "Actions";
"domain_list.unlink" = "Unlink Directory";
"domain_list.secure" = "Secure Domain";
"domain_list.unsecure" = "Unsecure Domain";
@ -221,6 +227,7 @@
"prefs.integrations" = "Integrations:";
"prefs.updates" = "Updates:";
"prefs.notifications" = "Notifications:";
"prefs.menu_contents" = "Features in Menu:";
"prefs.icon_options.php" = "Display PHP Icon";
"prefs.icon_options.elephant" = "Display Elephant Icon";
@ -246,7 +253,7 @@
"prefs.automatic_update_check_title" = "Automatically check for updates";
"prefs.automatic_update_check_desc" = "When checked, PHP Monitor will automatically check if there is a newer version available, and notify you if that is the case.";
"prefs.php_doctor_suggestions_title" = "Show suggestions (if applicable)";
"prefs.php_doctor_suggestions_title" = "Always show suggestions";
"prefs.php_doctor_suggestions_desc" = "If you uncheck this item, no PHP Doctor suggestions will appear in PHP Monitor's menu. Keep in mind that PHP Doctor will not appear if there are no recommendations.";
"prefs.shortcut_set" = "Set global shortcut";
@ -272,6 +279,33 @@
"prefs.notify_about_composer_success_desc" = "Displays a notification when the global Composer configuration was successfully updated.";
"prefs.notify_about_composer_success" = "Notify about global composer update";
"prefs.display_global_version_switcher_desc" = "If disabled, you will not be able to change the globally linked PHP version via the main menu.";
"prefs.display_global_version_switcher" = "PHP Switcher";
"prefs.display_services_manager_desc" = "If disabled, you will not be able to see, start or stop individual services. (If any services are disabled, you will not be easily able to see that this is the case.)";
"prefs.display_services_manager" = "Services Manager";
"prefs.display_valet_integration_desc" = "If disabled, you will not be able to locate the main Valet folder or open the list of domains.";
"prefs.display_valet_integration" = "Valet Integration";
"prefs.display_php_config_finder_desc" = "If disabled, you will not be able to easily locate your PHP configuration files and/or generate a phpinfo() dump.";
"prefs.display_php_config_finder" = "PHP Config Finder";
"prefs.display_composer_toolkit_desc" = "If disabled, you will not be able to invoke Composer via the main menu. The automatic Composer update after switching preference is unaffected by this change.";
"prefs.display_composer_toolkit" = "Composer Toolkit";
"prefs.display_limits_widget_desc" = "If disabled, you will not be able to see the limits widget (memory, POST, upload) in the main menu.";
"prefs.display_limits_widget" = "Limits Widget";
"prefs.display_extensions_desc" = "If disabled, you will not be able to easily toggle extensions via the main menu.";
"prefs.display_extensions" = "Extensions";
"prefs.display_presets_desc" = "If disabled, you will not be able to apply or revert PHP configuration presets.";
"prefs.display_presets" = "Presets";
"prefs.display_misc_desc" = "If disabled, you will not be able to access the First Aid & Services menu.";
"prefs.display_misc" = "First Aid & Services Menu";
// NOTIFICATIONS
"notification.version_changed_title" = "PHP %@ now active";
@ -365,7 +399,7 @@ problem manually, using your own Terminal app (this just shows you the output)."
// PHP FPM Broken
"alert.php_fpm_broken.title" = "Your PHP-FPM configuration is not pointing at the Valet socket!";
"alert.php_fpm_broken.info" = "PHP Monitor has determined that there are issues with your PHP-FPM config. This will result in '502 Bad Gateway' responses if you visit websites linked via Valet.";
"alert.php_fpm_broken.description" = "If it's been a while, you can usually fix this by running `valet install`, which updates your PHP-FPM configuration.\n\nIf you are seeing this message and you are trying to run a pre-release version of PHP, it is possible that Valet does not support this pre-release version of PHP yet.\n\nIf that is the case, you can try the following workaround: edit the file at `~/.composer/vendor/laravel/valet/cli/Valet/Brew.php` and add e.g. `php@8.2` to the `SUPPORTED_PHP_VERSIONS` array. After editing the file, try running `valet install`. (This will, if all goes well, set up all the required Valet configuration files.)";
"alert.php_fpm_broken.description" = "If it's been a while, you can usually fix this by running `valet install`, which updates your PHP-FPM configuration.\n\nIf you are seeing this message and you are trying to run a pre-release version of PHP, it is possible that Valet does not support this pre-release version of PHP yet.\n\nYou may need to upgrade your installation of Laravel Valet to at least v3.1.11, after that you should run `valet install`. More information here: https://phpmon.app/prerelease-php";
// PHP Monitor Cannot Start
"alert.cannot_start.title" = "PHP Monitor cannot start due to a problem with your system configuration";