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

Compare commits

...

65 Commits
v1.3 ... v2.5

Author SHA1 Message Date
4147cc7b4b 🚀 Version 2.5 2020-11-27 16:32:42 +01:00
72b309a716 📝 Be more explicit in main README 2020-11-27 16:31:54 +01:00
12c2716715 📝 Add information about running valet install 2020-11-27 16:29:02 +01:00
4d4019204b 📝 Updated README with new screenshot 2020-11-27 16:24:06 +01:00
157033a3b3 🚀 Version 2.5 (RC) 2020-11-27 11:32:26 +01:00
717cddacdd 🐛 Fix issue with php install now showing up 2020-11-27 11:29:40 +01:00
f13ed5dd90 Add option to open output of phpinfo() in browser 2020-11-27 01:33:06 +01:00
a8f823cd04 📝 Update README.md, ADDITIONAL.md 2020-11-26 18:59:49 +01:00
51fd22f595 Cleanup storyboard, add Brew package name to switcher 2020-11-26 18:53:15 +01:00
bf6ebff3bf Detect what version of PHP the php package is linked to 2020-11-26 17:58:34 +01:00
1887b19329 🚧 WIP: Get JSON about current PHP version 2020-11-26 17:02:24 +01:00
a53972404c 🚀 Version 2.4
* develop:
  📝 Updated SECURITY to reflect new support status
  🔧 Updated build settings
  🔧 Add PHP 8.0 to list of detected PHP versions
  📝 Universal application
2020-11-14 15:23:19 +01:00
64a605235a 📝 Updated SECURITY to reflect new support status 2020-11-14 02:33:46 +01:00
a194ecdebe 🔧 Updated build settings 2020-11-14 02:20:26 +01:00
03158a568c 🔧 Add PHP 8.0 to list of detected PHP versions 2020-11-14 02:19:42 +01:00
bdc6be7384 📝 Universal application 2020-11-14 02:19:28 +01:00
4908dba57e 📝 Update support information 2020-08-16 16:45:42 +02:00
0f0aa176b6 📝 Update README 2020-08-16 16:43:08 +02:00
485729f9a5 🚀 Version 2.3
Merge branch 'develop' into main

* develop:
  📝 Updated README, SECURITY docs
  🍱 Updated screenshot to Big Sur
  ♻️ Cleanup
  🚧 WIP: Notify the user about PHP switch completion (#15)
2020-08-16 16:27:38 +02:00
5eb36a9bdf 📝 Updated README, SECURITY docs 2020-07-20 17:28:59 +02:00
86113d2067 🍱 Updated screenshot to Big Sur 2020-07-19 13:17:55 +02:00
70c04d4dc7 ♻️ Cleanup
- Bump version number to 2.3
- Disable Metal validation in project configuration
- Added comments to various files to clarify code
- Moved various strings to Localizable.strings
- Removed references to old view controller for command log
- No longer log previously ran commands
2020-07-16 23:16:39 +02:00
5d69b423c1 🚧 WIP: Notify the user about PHP switch completion (#15) 2020-07-16 22:25:51 +02:00
1617b57b1e 🚀 Version 2.2
Merge branch 'develop'

* develop:
  📝 Update README
  📝 Amend modal copy
  🐛 Improved startup procedure
  🚧 Add Internet Access Policy
   Add shared scheme
2020-07-16 19:37:21 +02:00
33825e7b66 📝 Update README 2020-07-16 19:34:38 +02:00
464b7106b2 📝 Amend modal copy 2020-07-16 19:29:01 +02:00
0cf85f9958 🐛 Improved startup procedure
- First issue encountered will notify the user
- User has the option to Retry or Terminate the app
- Retry will go through launch checks again
- Some issues are marked as non-breaking, meaning the user will be
  notified, but the app will just start as usual
2020-07-16 19:26:29 +02:00
00cf2bc360 🚧 Add Internet Access Policy 2020-07-11 11:18:31 +02:00
dbb5329908 Add shared scheme 2020-07-11 10:54:05 +02:00
9fd8e7042a 🚀 Version 2.1
Merge branch 'develop'

* develop:
  📝 Add installation instructions
  📝 Added docs for release procedure
  📝 Updated README
  🔧 Hardened runtime (required for notarization)
  🍱 Updated icon for macOS 11 style, updated alert
2020-07-11 10:41:11 +02:00
cb62a20f2a 📝 Add installation instructions 2020-07-11 10:40:02 +02:00
bb1742a390 📝 Added docs for release procedure 2020-07-11 10:05:47 +02:00
6db43beddf 📝 Updated README 2020-07-11 01:07:51 +02:00
7e2c7cdd59 🔧 Hardened runtime (required for notarization) 2020-07-11 01:00:31 +02:00
e76dd0daeb 🍱 Updated icon for macOS 11 style, updated alert 2020-07-11 00:49:32 +02:00
0c33e8f8cc 🎉 Version 2.0 2020-05-17 11:36:31 +02:00
a6a196518a 🔀 Merge branch 'develop'
* develop:
  📝 Changes to the README, bump version number
  ♻️ Menu refactor
  ♻️ Use Localizable.strings
  ♻️ Change default shell for commands, cleanup files
2020-05-17 00:22:40 +02:00
a287ebf6e4 📝 Changes to the README, bump version number 2020-05-17 00:17:45 +02:00
2200b395f1 Merge branch 'cleanup' into develop
* cleanup:
  ♻️ Menu refactor
  ♻️ Use Localizable.strings
  ♻️ Change default shell for commands, cleanup files
2020-05-16 23:56:33 +02:00
bc8a572072 ♻️ Menu refactor 2020-05-16 23:55:04 +02:00
1c455ea05a ♻️ Use Localizable.strings 2020-05-16 23:32:34 +02:00
f2d01748be ♻️ Change default shell for commands, cleanup files
The shell that is now invoked is /bin/sh as opposed to /bin/bash, which
has proven to be faster in various cases.
2020-05-16 23:06:52 +02:00
d3e59e560f New beta for version 1.9 2020-05-13 17:34:51 +02:00
55e849c21b Various fixes and improvements
- Prevent crashes with incorrectly loading modules
- Prevent crashes when dyld libraries are missing
- Indicate when the PHP installation is broken
- New warning added at boot (multiple services)
- Added "Force load latest PHP version" option

It is known that PHP 5.6, 7.0 and 7.1 are causing issues with the
newer versions of certain libraries ("dyld library" warning).

It is recommended to only use PHP 7.2, PHP 7.3 and PHP 7.4 for a minimal
amount of issues. Otherwise, there are certain fixes that are possible
but they are not supported via PHP Monitor since they require manual
formula changes.

For more information, see:
https://github.com/eXolnet/homebrew-deprecated/issues/23#issuecomment-619976586
2020-05-13 17:26:00 +02:00
50f6afb3c5 🔀 Merge branch 'develop'
* develop:
   Add option to restart nginx, open valet config
   Allow restarting of PHP-FPM
  📝 Update copyright message
2020-02-20 17:24:48 +01:00
67e80aac8d Add option to restart nginx, open valet config 2020-02-20 17:24:21 +01:00
e683b6bc9a Allow restarting of PHP-FPM 2020-02-20 16:12:38 +01:00
e854ebe114 📝 Update copyright message 2020-02-20 11:58:16 +01:00
b792f55e5f 🔀 Merge branch 'develop'
* develop:
  ♻️ Change how the active PHP version is switched
2020-02-20 11:49:09 +01:00
cbf5526881 ♻️ Change how the active PHP version is switched 2020-02-20 11:47:49 +01:00
f62297ee3d 🔀 Merge branch 'develop'
* develop:
  🔥 Remove broken log
  🔧 Version bump
  🚧 WIP: Adds support to PHP 7.4
  ️ Use Command.execute to retrieve PHP version
  🚧 WIP: Add toggle to enable or disable Xdebug
  📝 Update readme
2019-12-20 11:59:04 +01:00
2f017f6732 📢 Version 1.6 for personal use 2019-12-20 11:55:25 +01:00
1c62a100af 🔥 Remove broken log
(Will just log this in a file - the right way)
2019-12-20 11:54:15 +01:00
e176d6fa2d 🔧 Version bump 2019-12-20 11:48:20 +01:00
ea60626c47 🚧 WIP: Adds support to PHP 7.4
(PHP 7.4 isn't out for Homebrew just yet.)
2019-11-28 12:46:14 +01:00
69c3386088 ️ Use Command.execute to retrieve PHP version 2019-11-20 08:43:09 +01:00
3e898d435b 🚧 WIP: Add toggle to enable or disable Xdebug 2019-10-17 19:04:49 +02:00
a4dcd0cd3d 📝 Update readme 2019-10-15 10:39:42 +02:00
bb382c0f74 📝 Update readme 2019-10-14 11:14:28 +02:00
c87c532d1c 📝 Update readme for Catalina compatibility 2019-10-14 10:54:03 +02:00
b26099369f Fix text position 2019-07-15 17:31:22 +02:00
90a8e7c336 Updated margins of shell output window 2019-07-15 17:25:57 +02:00
398dad9d5e Minor cleanup, shell output window title 2019-07-15 17:23:08 +02:00
48d0878dfd 📝 Adds LICENSE 2019-07-15 17:19:51 +02:00
4ba3415bf4 ♻️ phpmon -> PHP Monitor 2019-07-15 17:14:09 +02:00
45 changed files with 1226 additions and 374 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
phpmon.xcodeproj/project.xcworkspace phpmon.xcodeproj/project.xcworkspace
phpmon.xcodeproj/xcuserdata phpmon.xcodeproj/xcuserdata
PHP Monitor.xcodeproj/project.xcworkspace
PHP Monitor.xcodeproj/xcuserdata
.DS_Store

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Nico Verbruggen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -7,27 +7,35 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
C41C1B3922B0097F00E7CF16 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3822B0097F00E7CF16 /* LogViewController.swift */; };
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; }; C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; }; C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; }; C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; }; C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; };
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; }; C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; };
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; }; C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
C4F8C0A622D4FA41002EFE61 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C4F8C0A522D4FA41002EFE61 /* README.md */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
C41C1B3322B0097F00E7CF16 /* phpmon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = phpmon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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>"; };
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C41C1B3822B0097F00E7CF16 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C41C1B3D22B0098000E7CF16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; C41C1B3D22B0098000E7CF16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
C41C1B3F22B0098000E7CF16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; C41C1B3F22B0098000E7CF16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -36,10 +44,17 @@
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; }; C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; }; C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; }; C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = "<group>"; };
C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = "<group>"; };
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; }; C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; }; C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; }; C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; }; C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -56,10 +71,21 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
C405A4CD24B9B9070062FAFA /* IAP */ = {
isa = PBXGroup;
children = (
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */,
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */,
);
path = IAP;
sourceTree = "<group>";
};
C41C1B2A22B0097F00E7CF16 = { C41C1B2A22B0097F00E7CF16 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4F8C0A522D4FA41002EFE61 /* README.md */, C4F8C0A522D4FA41002EFE61 /* README.md */,
C4E713562570150F00007428 /* SECURITY.md */,
C4E713572570151400007428 /* docs */,
C41C1B3522B0097F00E7CF16 /* phpmon */, C41C1B3522B0097F00E7CF16 /* phpmon */,
C41C1B3422B0097F00E7CF16 /* Products */, C41C1B3422B0097F00E7CF16 /* Products */,
); );
@ -68,7 +94,7 @@
C41C1B3422B0097F00E7CF16 /* Products */ = { C41C1B3422B0097F00E7CF16 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C41C1B3322B0097F00E7CF16 /* phpmon.app */, C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -85,6 +111,8 @@
C41C1B3F22B0098000E7CF16 /* Info.plist */, C41C1B3F22B0098000E7CF16 /* Info.plist */,
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */, C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */, C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
C473319E2470923A009A0597 /* Localizable.strings */,
C405A4CD24B9B9070062FAFA /* IAP */,
); );
path = phpmon; path = phpmon;
sourceTree = "<group>"; sourceTree = "<group>";
@ -92,6 +120,7 @@
C41E181722CB61EB0072CF09 /* Classes */ = { C41E181722CB61EB0072CF09 /* Classes */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C47331A0247093AC009A0597 /* Menu */,
C4811D2722D70D8E00B5F6B3 /* Commands */, C4811D2722D70D8E00B5F6B3 /* Commands */,
C4811D2822D70D9C00B5F6B3 /* Helpers */, C4811D2822D70D9C00B5F6B3 /* Helpers */,
); );
@ -102,15 +131,23 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C41C1B3C22B0098000E7CF16 /* Main.storyboard */, C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
C41C1B3822B0097F00E7CF16 /* LogViewController.swift */,
); );
path = "View Controllers"; path = "View Controllers";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
C47331A0247093AC009A0597 /* Menu */ = {
isa = PBXGroup;
children = (
C47331A1247093B7009A0597 /* StatusMenu.swift */,
);
path = Menu;
sourceTree = "<group>";
};
C4811D2622D70CEF00B5F6B3 /* Singletons */ = { C4811D2622D70CEF00B5F6B3 /* Singletons */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C41C1B4622B009A400E7CF16 /* Shell.swift */, C41C1B4622B009A400E7CF16 /* Shell.swift */,
C42295DC2358D02000E263B2 /* Command.swift */,
C4811D2322D70A4700B5F6B3 /* App.swift */, C4811D2322D70A4700B5F6B3 /* App.swift */,
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */, C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
); );
@ -132,6 +169,8 @@
C476FF9722B0DD830098105B /* Alert.swift */, C476FF9722B0DD830098105B /* Alert.swift */,
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */, C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */,
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -140,6 +179,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */, C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */,
C46FA23E246C358E00944F05 /* StringExtension.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -147,9 +187,9 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
C41C1B3222B0097F00E7CF16 /* phpmon */ = { C41C1B3222B0097F00E7CF16 /* PHP Monitor */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "phpmon" */; buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "PHP Monitor" */;
buildPhases = ( buildPhases = (
C41C1B2F22B0097F00E7CF16 /* Sources */, C41C1B2F22B0097F00E7CF16 /* Sources */,
C41C1B3022B0097F00E7CF16 /* Frameworks */, C41C1B3022B0097F00E7CF16 /* Frameworks */,
@ -159,9 +199,9 @@
); );
dependencies = ( dependencies = (
); );
name = phpmon; name = "PHP Monitor";
productName = phpmon; productName = phpmon;
productReference = C41C1B3322B0097F00E7CF16 /* phpmon.app */; productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -171,7 +211,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 1020; LastSwiftUpdateCheck = 1020;
LastUpgradeCheck = 1020; LastUpgradeCheck = 1220;
ORGANIZATIONNAME = "Nico Verbruggen"; ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = { TargetAttributes = {
C41C1B3222B0097F00E7CF16 = { C41C1B3222B0097F00E7CF16 = {
@ -179,7 +219,7 @@
}; };
}; };
}; };
buildConfigurationList = C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "phpmon" */; buildConfigurationList = C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "PHP Monitor" */;
compatibilityVersion = "Xcode 9.3"; compatibilityVersion = "Xcode 9.3";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
@ -192,7 +232,7 @@
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
C41C1B3222B0097F00E7CF16 /* phpmon */, C41C1B3222B0097F00E7CF16 /* PHP Monitor */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -202,9 +242,11 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C4F8C0A622D4FA41002EFE61 /* README.md in Resources */,
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */, C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */,
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */, C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -219,13 +261,17 @@
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */, C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */, C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
C42295DD2358D02000E263B2 /* Command.swift in Sources */,
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */, C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */, C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
C41C1B3922B0097F00E7CF16 /* LogViewController.swift in Sources */, C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */, C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */, C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -270,6 +316,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -331,6 +378,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -364,16 +412,21 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist; INFOPLIST_FILE = phpmon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 2.5;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
@ -383,16 +436,21 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements; CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = 8M54J5J787; DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist; INFOPLIST_FILE = phpmon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 2.5;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
@ -400,7 +458,7 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "phpmon" */ = { C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "PHP Monitor" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
C41C1B4122B0098000E7CF16 /* Debug */, C41C1B4122B0098000E7CF16 /* Debug */,
@ -409,7 +467,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "phpmon" */ = { C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "PHP Monitor" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
C41C1B4422B0098000E7CF16 /* Debug */, C41C1B4422B0098000E7CF16 /* Debug */,

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
BuildableName = "PHP Monitor.app"
BlueprintName = "PHP Monitor"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
BuildableName = "PHP Monitor.app"
BlueprintName = "PHP Monitor"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
BuildableName = "PHP Monitor.app"
BlueprintName = "PHP Monitor"
ReferencedContainer = "container:PHP Monitor.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,28 +1,49 @@
# phpmon # PHP Monitor
phpmon is a macOS utility that runs on your Mac and displays the active PHP version in your status bar. <img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
<img src="./docs/screenshot.png" width="278px" alt="phpmon screenshot"/> PHP Monitor (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar.
For me, it comes in handy when running multiple versions of PHP with Homebrew and you wish to be able to see at a glance which version is currently linked & active with Laravel Valet, and switch between versions. It also gives you quick access to various useful functionality (like switching PHP versions, restarting services, accessing configuration files, and more).
This version of phpmon was developed for: <img src="./docs/screenshot.png" width="370px" alt="phpmon screenshot"/>
* macOS Mojave (10.14) For me, it comes in handy when running multiple versions of PHP with Homebrew. If you wish to be able to see at a glance which version is currently linked & active with Laravel Valet, PHP Monitor is your new best friend.
* PHP 7.x with Homebrew 2.x (with support for PHP 5.6 and PHP 7.0 [as well](https://github.com/eXolnet/homebrew-deprecated))
* Laravel Valet 2.2
## Why I built this It's also super convenient to switch between different versions of PHP, or to find your currently active .ini file!
## 🖥 System requirements
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
* macOS 10.15 Catalina or higher (works on macOS 11 Big Sur)
* The brew formula `php` has to be installed (which version it is, is detected)
* Laravel Valet 2.x
_Please note that future versions of PHP will not work automatically, minor changes are usually required to add support for newer versions of PHP. You may need to update your Valet installation to keep everything working if a major version update of PHP has been released._
## 🚀 How to install
You can install via Homebrew, or may download the latest [release][1].
To install via Homebrew, run:
brew tap nicoverbruggen/homebrew-cask
brew cask install phpmon
_The app is signed and notarized, meaning all you have to do is approve its first launch._
## 👨‍💻 Why I built this
I wanted to be able to see at a glance which version of PHP was linked, and handle dealing with Laravel Valet in a simple app without having to deal with the terminal every time. I wanted to be able to see at a glance which version of PHP was linked, and handle dealing with Laravel Valet in a simple app without having to deal with the terminal every time.
Initially, I had an Alfred workflow for this. But this does the job as well, while also showing me at all times which version of PHP is linked (which is the main benefit over e.g. an Alfred workflow). Initially, I had an Alfred workflow for this. But this does the job as well, while also showing me at all times which version of PHP is linked (which is the main benefit over e.g. an Alfred workflow).
## How it works ## 🚜 How it works
### Version detection ### Version detection
This utility runs `php -r 'print phpversion();'` in the background periodically (every 60 seconds) and extracts the version number. This utility runs `php -r 'print phpversion()'` in the background periodically (every 60 seconds).
### Switching PHP versions ### Switching PHP versions
@ -30,19 +51,53 @@ This utility will detect which PHP versions you have installed via Homebrew, and
This means: This means:
- You have at least the latest version of PHP installed (`php@7.3`) - You have at least the latest version of PHP installed (`php`)
- You have installed Laravel Valet (`which valet` returns `/usr/local/bin/valet`) - You have installed Laravel Valet (`which valet` returns `/usr/local/bin/valet`)
- You ran `valet trust`, which means Valet commands can be run without using sudo - You ran `valet trust`, which means Valet commands can be run without using sudo
The utility runs the following commands: The utility runs the following commands:
- Unlink all detected PHP versions - Unlink all detected PHP versions
- Switch to PHP 7.3 (this is done in order to ensure that Valet works, even when attempting to use PHP 5.6) - Switch to whatever version of PHP `php` is at (this is done to ensure that Valet works, even when attempting to use PHP 5.6)
- Tell Valet to switch to a specific PHP version - Stop all php-fpm service instances
- Link the desired version of PHP - Link the desired version of PHP
- Start the correct php-fpm service for the desired PHP version
### Want to know more? ### Want to know more?
If you want to know more about how this works, I recommend you check out the source code. If you want to know more about how this works, I recommend you check out the source code.
This app isn't very complicated after all. In the end, this just (conveniently) executes some shell commands. This app isn't very complicated after all. In the end, this just (conveniently) executes some shell commands.
## 🤬 Troubleshooting
**If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. Don't forget to run `valet install` after upgrading.**
PHP Monitor performs some integrity checks to ensure a good experience when using the app. You'll get a message telling you that PHP Monitor won't work correctly in the following scenarios:
- The PHP binary is not located in `/usr/local/bin/php`
- PHP is missing in `/usr/local/opt`
- Laravel Valet is missing in `/usr/local/bin/valet`
- Brew has not been added to sudoers in `/private/etc/sudoers.d/brew`
- Valet has not been added to sudoers in `/private/etc/sudoers.d/valet`
- Multiple PHP services are active (see more info below)
Follow instructions as specified in the alert in order to resolve any issues.
## 📝 Additional information & FAQ
Please consult the [additional information][2] file that contains more information. It has answers to additional questions and more information to troubleshoot your problem.
## ⭐️ Star me!
If this software has been useful to you, I ask that you **please star the repository**, so I know that the software is being used.
I did not include any tracking or analytics software, so if you encounter issues, let me know [via an issue](https://github.com/nicoverbruggen/phpmon/issues/new).
## 💵 Support me?
I develop this application in my spare time, after work. If you find the application useful and you have a bit of money to spare, feel free to send me [a tip via PayPal][3].
[1]: https://github.com/nicoverbruggen/phpmon/releases
[2]: docs/ADDITIONAL.md
[3]: https://paypal.me/nicoverbruggen

15
SECURITY.md Normal file
View File

@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
The following versions of PHP Monitor are supported:
| Version | Universal | Supported | Runs on macOS |
| ------- | ------------- | ------------------ | ----- |
| 2.5 | ✅ | ✅ | Catalina (10.15), Big Sur (11.0) |
| 2.4 | ✅ | ❌ | Catalina (10.15), Big Sur (11.0) |
| < 2.4 | | | Catalina (10.15) |
## Reporting a Vulnerability
Contact Nico Verbruggen at the email address used for the commits in the repository. Please include "PHP Monitor" in the subject.

Binary file not shown.

111
docs/ADDITIONAL.md Normal file
View File

@ -0,0 +1,111 @@
### Q&A
#### Q: Does this support Apple Silicon?
Yes. This is a universal app.
#### Q: Is PHP 8.x supported?
Yes.
#### Q: This app is doing network requests? Why?
It's Homebrew. I can't prevent `brew` from doing things via the network when I invoke it.
PHP Monitor itself doesn't do any network requests. Feel free to check the source code or intercept the traffic, if you don't believe me.
#### Q: How can I set this up on a fresh Mac?
If you want to set up your computer for the very first time, here's how I do it:
Install [Homebrew](https://brew.sh) first.
Install PHP, composer, add to path:
brew install php
brew install composer
nano .zshrc
Make sure the following line is not in the comments:
export PATH=$HOME/bin:/usr/local/bin:$PATH
and add the following to your .zshrc:
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
Make sure PHP is linked correctly:
which php
should return: `/usr/local/bin/php`
composer global require laravel/valet
valet install
This should install `dnsmasq` and set up Valet. Great, almost there!
valet trust
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
#### Q: I want PHP Monitor to start up when I boot my Mac!
You can do this by dragging *PHP Monitor.app* into the **Login Items** section in **System Preferences > Users & Groups** for your account.
Super convenient!
#### Q: PHP Monitor says that the latest version of PHP is not installed, but it is!
Try installing again using `brew install php`.
This should resolve the issue.
#### Q: PHP Monitor says the correct version is loaded, but my Valet sites don't work!
You may need to run `valet install`. (Preferably after updating `valet` by running `composer global update`).
#### Q: PHP Monitor reports another version compared to phpinfo on my local website, what is going on?
_Beginning with version 2.0 you'll get alerts about this at startup._
If you're still seeing another version of PHP in your scripts running on your local webserver (nginx) — e.g. when running `phpinfo()` — I recommend you shut down all PHP services that are currently active. You can find out what services are active by running:
sudo brew services list | grep php
This will present to you a list of services, like so (depending on the installed versions of PHP):
```
php started root /Library/LaunchDaemons/homebrew.mxcl.php.plist
php@5.6 stopped
php@7.0 stopped
php@7.1 stopped
php@7.2 stopped
php@7.3 stopped
```
You'll want to make sure that **only one service is running** and that it is running **as `root`**. You can terminate a service by running:
sudo brew services stop {service_name}
So in order to disable PHP 7.3, you'd need to run:
sudo brew services stop php@7.3
If you notice that PHP FPM is running as your own user account, you can turn off the service by running:
brew services stop php@7.3
The easiest way to make sure that PHP Monitor works again is to run the following commands:
sudo brew services stop php
sudo brew services stop php@7.3
sudo brew services stop php@7.2
sudo brew services stop php@7.1
sudo brew services stop php@7.0
sudo brew services stop php@5.6
sudo brew services stop nginx
Then, in PHP Monitor, select "Restart php-fpm service", which should start the service.
Alternatively, you can run `sudo brew services start php@7.4` where `7.4` is your preferred version of PHP (for the latest version of PHP, you may omit `@7.4` like in the example above).

13
docs/RELEASE.md Normal file
View File

@ -0,0 +1,13 @@
# Release Procedure
1. Merge into `main`
2. Create tag
3. Add changes to changelog
4. Archive
5. Notarize and prepare for own distribution
6. After notarization, export .app
7. Create zipped version
8. Calculate SHA256: `openssl dgst -sha256 phpmon-2.x.zip`
9. Upload to GitHub
10. Update Cask
11. Check new version can be installed via Cask

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 270 KiB

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:phpmon.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,23 +1,43 @@
// //
// AppDelegate.swift // AppDelegate.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/06/2019. // Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
// //
import Cocoa import Cocoa
import UserNotifications
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {
// MARK: - Variables // MARK: - Variables
/**
The Shell singleton that keeps track of the history of all
(invoked by PHP Monitor) shell commands. It is used to
invoke all commands in this application.
*/
let sharedShell : Shell let sharedShell : Shell
/**
The App singleton contains information about the state of
the application and global variables.
*/
let state : App let state : App
/**
The MainMenu singleton is responsible for rendering the
menu bar item and its menu, as well as its actions.
*/
let menu : MainMenu let menu : MainMenu
// MARK: - Initializer // MARK: - Initializer
/**
When the application initializes, create all singletons.
*/
override init() { override init() {
self.sharedShell = Shell.user self.sharedShell = Shell.user
self.state = App.shared self.state = App.shared
@ -26,13 +46,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
// MARK: - Lifecycle // MARK: - Lifecycle
/**
When the application has finished launching, we'll want to set up
the user notification center delegate, and kickoff the menu
startup procedure.
*/
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
NSUserNotificationCenter.default.delegate = self
self.menu.startup() self.menu.startup()
} }
func applicationWillTerminate(_ aNotification: Notification) { // MARK: - NSUserNotificationCenterDelegate
// Insert code here to tear down your application
/**
When a notification is sent, the delegate of the notification center
is asked whether the notification should be presented or not. Since
the user can now disable notifications per application since macOS
Catalina, any and all notifications should be displayed.
*/
func userNotificationCenter(
_ center: NSUserNotificationCenter,
shouldPresent notification: NSUserNotification
) -> Bool {
return true
} }
} }

View File

@ -1,68 +1,68 @@
{ {
"images" : [ "images" : [
{ {
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16.png", "filename" : "icon_16x16.png",
"scale" : "1x" "idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
}, },
{ {
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16@2x.png", "filename" : "icon_16x16@2x.png",
"scale" : "2x" "idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
}, },
{ {
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32.png", "filename" : "icon_32x32.png",
"scale" : "1x" "idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
}, },
{ {
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32@2x.png", "filename" : "icon_32x32@2x.png",
"scale" : "2x" "idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
}, },
{ {
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128.png", "filename" : "icon_128x128.png",
"scale" : "1x" "idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
}, },
{ {
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128@2x.png", "filename" : "icon_128x128@2x.png",
"scale" : "2x" "idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
}, },
{ {
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256.png", "filename" : "icon_256x256.png",
"scale" : "1x" "idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
}, },
{ {
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256@2x.png", "filename" : "icon_256x256@2x.png",
"scale" : "2x" "idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
}, },
{ {
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png", "filename" : "icon_512x512.png",
"scale" : "1x" "idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
}, },
{ {
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512@2x.png", "filename" : "icon_512x512@2x.png",
"scale" : "2x" "idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
} }
], ],
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 315 KiB

View File

@ -1,6 +1,6 @@
// //
// Services.swift // Services.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/06/2019. // Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -14,34 +14,130 @@ class Actions {
public static func detectPhpVersions() -> [String] { public static func detectPhpVersions() -> [String] {
let files = Shell.user.pipe("ls /usr/local/opt | grep php@") let files = Shell.user.pipe("ls /usr/local/opt | grep php@")
var versions = files.components(separatedBy: "\n") var versions = files.components(separatedBy: "\n")
// Remove all empty strings // Remove all empty strings
versions.removeAll { (string) -> Bool in versions.removeAll { (string) -> Bool in
return (string == "") return (string == "")
} }
// Get a list of versions only // Get a list of versions only
var versionsOnly : [String] = [] var versionsOnly : [String] = []
versions.forEach { (string) in versions.forEach { (string) in
versionsOnly.append(string.components(separatedBy: "php@")[1]) versionsOnly.append(string.components(separatedBy: "php@")[1])
} }
// Make sure the aliased version is detected
// The user may have `php` installed, but not e.g. `php@8.0`
// We should also detect that as a version that is installed
let phpAlias = App.shared.brewPhpVersion
if (!versionsOnly.contains(phpAlias)) {
versionsOnly.append(phpAlias);
}
return versionsOnly return versionsOnly
} }
public static func restartPhpFpm() {
let version = App.shared.currentVersion!.short
if (version == App.shared.brewPhpVersion) {
Shell.user.run("sudo brew services restart php")
} else {
Shell.user.run("sudo brew services restart php@\(version)")
}
}
public static func restartNginx()
{
Shell.user.run("sudo brew services restart nginx")
}
public static func switchToPhpVersion(version: String, availableVersions: [String]) { public static func switchToPhpVersion(version: String, availableVersions: [String]) {
availableVersions.forEach { (version) in availableVersions.forEach { (version) in
// Unlink the current version
Shell.user.run("brew unlink php@\(version)") Shell.user.run("brew unlink php@\(version)")
} // Stop the services
if (availableVersions.contains("7.3")) { if (version == App.shared.brewPhpVersion) {
Shell.user.run("brew link php@7.3") Shell.user.run("sudo brew services stop php")
if (version == Constants.LatestPhpVersion) {
Shell.user.run( "valet use php")
} else { } else {
Shell.user.run("valet use php@\(version)") Shell.user.run("sudo brew services stop php@\(version)")
}
}
if (availableVersions.contains(App.shared.brewPhpVersion)) {
// Use the latest version as a default
Shell.user.run("brew link php@\(App.shared.brewPhpVersion) --overwrite --force")
if (version == App.shared.brewPhpVersion) {
// If said version was also requested, all we need to do is start the service
Shell.user.run("sudo brew services start php")
} else {
// Otherwise, link the correct php version + start the correct service
Shell.user.run("brew link php@\(version) --overwrite --force")
Shell.user.run("sudo brew services start php@\(version)")
} }
} }
} }
public static func openGenericPhpConfigFolder() {
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openPhpConfigFolder(version: String) { public static func openPhpConfigFolder(version: String) {
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")]; let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL]); NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openValetConfigFolder() {
let files = [NSURL(fileURLWithPath: NSString(string: "~/.config/valet").expandingTildeInPath)];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func didFindXdebug(_ version: String) -> Bool {
let command = """
grep -q 'zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
"""
let output = Shell.user.pipe(command).trimmingCharacters(in: .whitespacesAndNewlines)
return (output == "YES")
}
public static func didEnableXdebug(_ version: String) -> Bool {
let command = """
grep -q '; zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
"""
let output = Shell.user.pipe(command).trimmingCharacters(in: .whitespacesAndNewlines)
return (output == "NO")
}
public static func toggleXdebug() {
let version = App.shared.currentVersion?.short
var command = """
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
"""
if (self.didEnableXdebug(version!)) {
command = """
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
"""
}
Shell.user.run(command)
}
// unlink all the crap and link the latest version
// this also restarts all services
public static func fixMyPhp() {
let versions = self.detectPhpVersions()
versions.forEach { (version) in
Shell.user.run("brew unlink php@\(version)")
if (version == App.shared.brewPhpVersion) {
Shell.user.run("brew services stop php")
Shell.user.run("sudo brew services stop php")
} else {
Shell.user.run("brew services stop php@\(version)")
Shell.user.run("sudo brew services stop php@\(version)")
}
}
Shell.user.run("brew services stop php")
Shell.user.run("brew services stop nginx")
Shell.user.run("brew link php")
Shell.user.run("sudo brew services restart php")
Shell.user.run("sudo brew services restart nginx")
} }
} }

View File

@ -1,6 +1,6 @@
// //
// Environment.swift // Environment.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 12/06/2019. // Created by Nico Verbruggen on 12/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -10,51 +10,122 @@ import Foundation
class Startup { class Startup {
public static func checkEnvironment() public var failed : Bool = false
public var failureCallback = {}
/**
Checks the user's environment and checks if PHP Monitor can be used properly.
This checks if PHP is installed, Valet is running, the appropriate permissions are set, and more.
- Parameter success: Callback that is fired if the application can proceed with launch
- Parameter failure: Callback that is fired if the application must retry launch
*/
public func checkEnvironment(success: () -> Void, failure: @escaping () -> Void)
{ {
self.presentAlertOnMainThreadIf( self.failureCallback = failure
self.performEnvironmentCheck(
!Shell.user.pipe("which php").contains("/usr/local/bin/php"), !Shell.user.pipe("which php").contains("/usr/local/bin/php"),
messageText: "PHP is not correctly installed", messageText: "PHP is not correctly installed",
informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue." informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)",
breaking: true
) )
self.presentAlertOnMainThreadIf( self.performEnvironmentCheck(
!Shell.user.pipe("ls /usr/local/opt | grep php@7.3").contains("php@7.3"), !Shell.user.pipe("ls /usr/local/opt | grep php").contains("php"),
messageText: "PHP 7.3 is not correctly installed", messageText: "PHP is not correctly installed",
informativeText: "PHP 7.3 alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue." informativeText: "PHP alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue. If you already have the `php` formula installed, you may need to run `brew install php` in order for PHP Monitor to detect this installation.",
breaking: true
) )
self.presentAlertOnMainThreadIf( self.performEnvironmentCheck(
!Shell.user.pipe("which valet").contains("/usr/local/bin/valet"), !Shell.user.pipe("which valet").contains("/usr/local/bin/valet"),
messageText: "Laravel Valet is not correctly installed", messageText: "Laravel Valet is not correctly installed",
informativeText: "You must install Valet via brew. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue." informativeText: "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue.",
breaking: true
) )
self.presentAlertOnMainThreadIf( self.performEnvironmentCheck(
!Shell.user.pipe("cat /private/etc/sudoers.d/brew").contains("/usr/local/bin/brew"), !Shell.user.pipe("cat /private/etc/sudoers.d/brew").contains("/usr/local/bin/brew"),
messageText: "Brew has not been added to sudoers.d", messageText: "Brew has not been added to sudoers.d",
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue." informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.",
breaking: true
) )
self.presentAlertOnMainThreadIf( self.performEnvironmentCheck(
!Shell.user.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet"), !Shell.user.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet"),
messageText: "Valet has not been added to sudoers.d", messageText: "Valet has not been added to sudoers.d",
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue." informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.",
breaking: true
) )
let services = Shell.user.pipe("brew services list | grep php")
self.performEnvironmentCheck(
(services.countInstances(of: "started") > 1),
messageText: "Multiple PHP services are active",
informativeText: "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes." +
"\n\nThe easiest solution is to choose the option 'Force load latest PHP version' in the menu bar." +
"\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies)." +
"\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue." +
"\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.",
breaking: false
)
if (!self.failed) {
self.determineBrewAliasVersion()
success()
}
} }
private static func presentAlertOnMainThreadIf( /**
* In order to avoid having to hard-code which version of PHP is aliased to what specific subversion,
* PHP Monitor now determines the alias by checking the user's system.
*/
private func determineBrewAliasVersion()
{
print("PHP Monitor has determined the application has successfully passed all checks.")
print("Determining which version of PHP is aliased to `php` via Homebrew...")
let brewPhpAlias = Shell.user.pipe("brew info php --json");
App.shared.brewPhpPackage = try! JSONDecoder().decode(
[HomebrewPackage].self,
from: brewPhpAlias.data(using: .utf8)!
).first!
print("When on your system, the `php` formula means version \(App.shared.brewPhpVersion)!")
}
/**
* Perform an environment check. Will cause the application to terminate, if `breaking` is set to true.
*
* - Parameter condition: Condition to check for
* - Parameter messageText: Short description of what is wrong
* - Parameter informativeText: Expanded description of the environment check that failed
* - Parameter breaking: If the application should terminate afterwards
*/
private func performEnvironmentCheck(
_ condition: Bool, _ condition: Bool,
messageText: String, messageText: String,
informativeText: String informativeText: String,
breaking: Bool
) )
{ {
if (condition) { if (condition) {
// Only breaking issues will cause the notification
if (breaking) {
self.failed = true
}
DispatchQueue.main.async { DispatchQueue.main.async {
Alert.present( // Present the information to the user
_ = Alert.present(
messageText: messageText, messageText: messageText,
informativeText: informativeText informativeText: informativeText
) )
// Only breaking issues will throw the extra retry modal
if (breaking) {
self.failureCallback()
}
} }
} }
} }

View File

@ -1,6 +1,6 @@
// //
// Alert.swift // Alert.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 12/06/2019. // Created by Nico Verbruggen on 12/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -12,12 +12,16 @@ class Alert {
public static func present( public static func present(
messageText: String, messageText: String,
informativeText: String, informativeText: String,
buttonTitle: String = "OK" buttonTitle: String = "OK",
) { secondButtonTitle: String = ""
) -> Bool {
let alert = NSAlert.init() let alert = NSAlert.init()
alert.messageText = messageText alert.messageText = messageText
alert.informativeText = informativeText alert.informativeText = informativeText
alert.addButton(withTitle: buttonTitle) alert.addButton(withTitle: buttonTitle)
alert.runModal() if (!secondButtonTitle.isEmpty) {
alert.addButton(withTitle: secondButtonTitle)
}
return alert.runModal() == .alertFirstButtonReturn
} }
} }

View File

@ -0,0 +1,19 @@
//
// HomebrewPackage.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 26/11/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Foundation
struct HomebrewPackage : Decodable {
let name: String
let full_name: String
let aliases: [String]
public func getVersion() -> String {
return aliases.first!.replacingOccurrences(of: "php@", with: "")
}
}

View File

@ -0,0 +1,19 @@
//
// LocalNotification.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/07/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Foundation
class LocalNotification {
public static func send(title: String, subtitle: String)
{
let notification = NSUserNotification()
notification.title = title
notification.subtitle = subtitle
NSUserNotificationCenter.default.deliver(notification)
}
}

View File

@ -1,6 +1,6 @@
// //
// ImageGenerator.swift // ImageGenerator.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/06/2019. // Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -39,7 +39,7 @@ class MenuBarImageGenerator {
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
// Position the text inside the image rect // Position the text inside the image rect
let textRect = CGRect(x: padding, y: 0, width: image.size.width, height: image.size.height) let textRect = CGRect(x: padding, y: 0.5, width: image.size.width, height: image.size.height)
let targetImage: NSImage = NSImage(size: image.size) let targetImage: NSImage = NSImage(size: image.size)
let rep: NSBitmapImageRep = NSBitmapImageRep( let rep: NSBitmapImageRep = NSBitmapImageRep(

View File

@ -1,6 +1,6 @@
// //
// PhpVersionExtractor.swift // PhpVersionExtractor.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/06/2019. // Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -13,17 +13,36 @@ class PhpVersion {
var short : String = "???" var short : String = "???"
var long : String = "???" var long : String = "???"
var xdebugFound: Bool = false
var xdebugEnabled : Bool = false
var error : Bool = false
init() { init() {
let version = Shell.user let version = Command.execute(path: "/usr/local/bin/php", arguments: ["-r", "print phpversion();"])
// Get the version directly from PHP
.pipe("php -r 'print phpversion();'") if (version == "" || version.contains("Warning")) {
self.short = "💩 BROKEN"
self.long = "";
self.error = true
return;
}
// That's the long version // That's the long version
self.long = version self.long = version
// Next up, let's strip away the minor version number // Next up, let's strip away the minor version number
let segments = long.components(separatedBy: ".") let segments = long.components(separatedBy: ".")
// Get the first two elements // Get the first two elements
self.short = segments[0...1].joined(separator: ".") self.short = segments[0...1].joined(separator: ".")
// Load xdebug support
self.xdebugFound = Actions.didFindXdebug(self.short)
if (self.xdebugFound) {
self.xdebugEnabled = Actions.didEnableXdebug(self.short)
}
self.error = false;
} }
} }

View File

@ -0,0 +1,91 @@
//
// MainMenuBuilder.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/05/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Cocoa
class StatusMenu : NSMenu {
public func addPhpVersionMenuItems()
{
var string = "mi_unsure".localized
if (App.shared.currentVersion != nil) {
if (!App.shared.currentVersion!.error) {
// in case the php version loaded without issue
string = "\("mi_php_version".localized) \(App.shared.currentVersion!.long)"
self.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
} else {
// in case of an error show the error message
["mi_php_broken_1", "mi_php_broken_2",
"mi_php_broken_3", "mi_php_broken_4"].forEach { (message) in
self.addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
}
}
}
}
public func addPhpActionMenuItems()
{
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
var shortcutKey = 1
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
let version = App.shared.availablePhpVersions[index]
let action = #selector(MainMenu.switchToPhpVersion(sender:))
let brew = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
let menuItem = NSMenuItem(title: "\("mi_php_switch".localized) \(version) (\(brew))", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.tag = index
shortcutKey = shortcutKey + 1
self.addItem(menuItem)
}
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_active_services".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "f"))
self.addItem(NSMenuItem(title: "mi_restart_nginx".localized, action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
self.addItem(NSMenuItem(title: "mi_force_load_latest".localized, action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: ""))
}
if (App.shared.busy) {
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
}
}
public func addPhpConfigurationMenuItems()
{
if (App.shared.currentVersion != nil) {
self.addItem(NSMenuItem(title: "mi_configuration".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
self.addItem(NSMenuItem(title: "mi_php_config".localized, action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c"))
self.addItem(NSMenuItem(title: "mi_phpinfo".localized, action: #selector(MainMenu.openPhpInfo), keyEquivalent: "i"))
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_enabled_extensions".localized, action: nil, keyEquivalent: ""))
self.addXdebugMenuItem()
}
}
private func addXdebugMenuItem()
{
let xdebugFound = App.shared.currentVersion!.xdebugFound
if (xdebugFound) {
let xdebugOn = App.shared.currentVersion!.xdebugEnabled
let xdebugToggleMenuItem = NSMenuItem(
title: "mi_xdebug".localized,
action: #selector(MainMenu.toggleXdebug), keyEquivalent: "x"
)
if (xdebugOn) {
xdebugToggleMenuItem.state = .on
}
self.addItem(xdebugToggleMenuItem)
} else {
let disabledItem = NSMenuItem(
title: "mi_xdebug_missing".localized,
action: nil, keyEquivalent: "x"
)
disabledItem.isEnabled = false
self.addItem(disabledItem)
}
}
}

View File

@ -1,6 +1,6 @@
// //
// Constants.swift // Constants.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 08/07/2019. // Created by Nico Verbruggen on 08/07/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -12,15 +12,17 @@ class Constants {
/** /**
* The PHP versions supported by this application. * The PHP versions supported by this application.
* Versions that do not appear in this array are omitted from the list.
*/ */
static let SupportedPhpVersions = [ static let SupportedPhpVersions = [
"5.6", "7.0", "7.1", "7.2", "7.3" "5.6",
"7.0",
"7.1",
"7.2",
"7.3",
"7.4",
"8.0",
"8.1"
] ]
/**
Which php version is aliased as `php` to brew?
This is usually the latest PHP version.
*/
static let LatestPhpVersion = "7.3"
} }

View File

@ -1,6 +1,6 @@
// //
// Date.swift // Date.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 09/07/2019. // Created by Nico Verbruggen on 09/07/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -10,8 +10,7 @@ import Cocoa
extension Date extension Date
{ {
func toString() -> String func toString() -> String {
{
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.string(from: self) return dateFormatter.string(from: self)

View File

@ -0,0 +1,32 @@
//
// StringExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/05/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Foundation
extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
func countInstances(of stringToFind: String) -> Int {
if (stringToFind.isEmpty) {
return 0
}
var count = 0
var searchRange: Range<String.Index>?
while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
count += 1
searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
}
return count
}
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ApplicationDescription</key>
<string>PHP Monitor is a tool that shows the active PHP version in your menu bar and gives you easy access to certain PHP service actions and config files.</string>
<key>DeveloperName</key>
<string>Nico Verbruggen</string>
<key>Website</key>
<string>https://github.com/nicoverbruggen/phpmon</string>
<key>Connections</key>
<array>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>registry.npmjs.org</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>80, 443</string>
<key>Relevance</key>
<string>Essential</string>
<key>Purpose</key>
<string>PHP Monitor directly invokes Homebrew which contacts the NPM Registry.</string>
<key>DenyConsequences</key>
<string>If you deny these connections, PHP Monitor might not be able to complete its preset set of instructions, causing version switching to fail.</string>
</dict>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>github.com, api.github.com</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>443</string>
<key>Relevance</key>
<string>Essential</string>
<key>Purpose</key>
<string>PHP Monitor directly invokes Homebrew which contacts GitHub. This happens when PHP Monitor asks for more information about the PHP formula to determine which version of PHP you&apos;ve got running.</string>
<key>DenyConsequences</key>
<string>If you deny these connections, PHP Monitor might not be able to complete its preset set of instructions, causing version switching to fail.</string>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,2 @@
// Top-level, general application description:
"ApplicationDescription" = "PHP Monitor is a tool that shows the active PHP version in your menu bar and gives you easy access to certain PHP service actions and config files.";

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.3</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>10</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string> <string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
@ -27,7 +27,7 @@
<key>LSUIElement</key> <key>LSUIElement</key>
<true/> <true/>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Nico Verbruggen. All rights reserved.</string> <string>Copyright © 2020 Nico Verbruggen. All rights reserved.</string>
<key>NSMainStoryboardFile</key> <key>NSMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>

View File

@ -0,0 +1,56 @@
/*
Strings.strings
PHP Monitor
Created by Nico Verbruggen on 16/05/2020.
Copyright © 2020 Nico Verbruggen. All rights reserved.
*/
// MENU ITEMS (MI)
"mi_busy" = "PHP Monitor is busy...";
"mi_unsure" = "We are not sure what version of PHP you are running.";
"mi_php_version" = "You are running PHP";
"mi_php_switch" = "Switch to PHP";
"mi_php_broken_1" = "Oof! It appears your PHP installation is broken...";
"mi_php_broken_2" = "Try running `php -v` in your terminal.";
"mi_php_broken_3" = "You could also try switching to another version.";
"mi_php_broken_4" = "Running `brew reinstall php` (or for the equivalent version) might help.";
"mi_active_services" = "Active Services";
"mi_restart_php_fpm" = "Restart php-fpm service";
"mi_restart_nginx" = "Restart nginx service";
"mi_force_load_latest" = "Force load latest PHP version";
"mi_configuration" = "Configuration";
"mi_valet_config" = "Locate Valet folder (.config/valet)";
"mi_php_config" = "Locate PHP configuration file (php.ini)";
"mi_phpinfo" = "Show current configuration (phpinfo)";
"mi_enabled_extensions" = "Enabled Extensions";
"mi_xdebug" = "Xdebug";
"mi_xdebug_missing" = "xdebug.so missing";
"mi_quit" = "Quit PHP Monitor";
"mi_about" = "About PHP Monitor";
// NOTIFICATIONS
"notification.version_changed_title" = "PHP %@ now active";
"notification.version_changed_desc" = "PHP Monitor has finished the switch to PHP %@.";
// ALERTS
// Force Reload Started
"alert.force_reload.title" = "PHP Monitor will force reload the latest version of PHP";
"alert.force_reload.info" = "This can take a while. You'll get another alert when the force reload has completed.";
// Force Reload Done
"alert.force_reload_done.title" = "PHP has been force reloaded";
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP.";
// PHP Monitor Cannot Start
"alert.cannot_start.title" = "PHP Monitor cannot start";
"alert.cannot_start.info" = "The issue you were just notified about is keeping PHP Monitor from functioning correctly. Please fix the issue and restart PHP Monitor. After clicking on OK, PHP Monitor will close.\n\nIf you have fixed the issue (or don't remember what the exact issue is) you can click on Retry, which will have PHP Monitor retry the startup checks.";
"alert.cannot_start.close" = "Close";
"alert.cannot_start.retry" = "Retry";

View File

@ -1,6 +1,6 @@
// //
// StateManager.swift // StateManager.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/07/2019. // Created by Nico Verbruggen on 11/07/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -33,8 +33,23 @@ class App {
var timer: Timer? var timer: Timer?
/** /**
The window controller that will show the log. Information we were able to discern from the Homebrew info command (as JSON).
*/ */
var windowController: NSWindowController? = nil var brewPhpPackage: HomebrewPackage? = nil {
didSet {
self.brewPhpVersion = self.brewPhpPackage!.getVersion()
}
}
/**
The version that the `php` formula via Brew is aliased to on the current system.
If you're up to date, `php` will be aliased to the latest version,
but that might not be the case.
We'll technically default to version 8.0, but the information should always be loaded
from Homebrew itself upon starting the application.
*/
var brewPhpVersion: String = "8.0"
} }

View File

@ -0,0 +1,33 @@
//
// Command.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 17/10/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved.
//
import Cocoa
class Command {
/**
Immediately executes a command.
- Parameter path: The path of the command or program to invoke.
- Parameter arguments: A list of arguments that are passed on.
*/
public static func execute(path: String, arguments: [String]) -> String {
let task = Process()
task.launchPath = path
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
return output;
}
}

View File

@ -1,6 +1,6 @@
// //
// MainMenu.swift // MainMenu.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/07/2019. // Created by Nico Verbruggen on 11/07/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -12,80 +12,117 @@ class MainMenu: NSObject, NSWindowDelegate {
static let shared = MainMenu() static let shared = MainMenu()
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) /**
The status bar item with variable length.
*/
let statusItem = NSStatusBar.system.statusItem(
withLength: NSStatusItem.variableLength
)
// MARK: - UI related // MARK: - UI related
/**
Kick off the startup of the rendering of the main menu.
*/
public func startup() { public func startup() {
// Start with the icon // Start with the icon
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!) self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
// Perform environment boot checks // Perform environment boot checks
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
Startup.checkEnvironment() Startup().checkEnvironment(success: {
App.shared.availablePhpVersions = Actions.detectPhpVersions() self.onEnvironmentPass()
self.updatePhpVersionInStatusBar() }, failure: {
// Schedule a request to fetch the PHP version every 60 seconds self.onEnvironmentFail()
DispatchQueue.main.async { })
App.shared.timer = Timer.scheduledTimer( }
timeInterval: 60, }
target: self,
selector: #selector(self.updatePhpVersionInStatusBar), /**
userInfo: nil, When the environment is all clear and the app can run, let's go.
repeats: true */
) private func onEnvironmentPass() {
App.shared.availablePhpVersions = Actions.detectPhpVersions()
self.updatePhpVersionInStatusBar()
// Schedule a request to fetch the PHP version every 60 seconds
DispatchQueue.main.async {
App.shared.timer = Timer.scheduledTimer(
timeInterval: 60,
target: self,
selector: #selector(self.updatePhpVersionInStatusBar),
userInfo: nil,
repeats: true
)
}
}
/**
When the environment is not OK, present an alert to inform the user.
*/
private func onEnvironmentFail() {
DispatchQueue.main.async {
let close = Alert.present(
messageText: "alert.cannot_start.title".localized,
informativeText: "alert.cannot_start.info".localized,
buttonTitle: "alert.cannot_start.close".localized,
secondButtonTitle: "alert.cannot_start.retry".localized
)
if (!close) {
self.startup()
} else {
exit(1)
} }
} }
} }
/**
Update the menu's contents, based on what's going on.
*/
public func update() { public func update() {
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
let menu = NSMenu() // Create a new menu
var string = "We are not sure what version of PHP you are running." let menu = StatusMenu()
if (App.shared.currentVersion != nil) {
string = "You are running PHP \(App.shared.currentVersion!.long)" // Add the PHP versions (or error messages)
} menu.addPhpVersionMenuItems()
menu.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
var shortcutKey = 1 // Add the possible actions
for index in (0..<App.shared.availablePhpVersions.count).reversed() { menu.addPhpActionMenuItems()
let version = App.shared.availablePhpVersions[index]
let action = #selector(self.switchToPhpVersion(sender:))
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.tag = index
shortcutKey = shortcutKey + 1
menu.addItem(menuItem)
}
menu.addItem(NSMenuItem.separator())
}
if (App.shared.busy) {
menu.addItem(NSMenuItem(title: "Switching PHP versions...", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
}
if (App.shared.currentVersion != nil) {
menu.addItem(NSMenuItem(title: "PHP configuration file (php.ini)", action: #selector(self.openActiveConfigFolder), keyEquivalent: ""))
}
menu.addItem(NSMenuItem(title: "View shell output", action: #selector(self.openOutput), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "About phpmon", action: #selector(self.openAbout), keyEquivalent: ""))
// Add information about services & actions
menu.addPhpConfigurationMenuItems()
menu.addItem(NSMenuItem.separator())
// Add about & quit menu items
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(self.openAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(self.terminateApp), keyEquivalent: "q"))
// Make sure every item can be interacted with
menu.items.forEach({ (item) in menu.items.forEach({ (item) in
item.target = self item.target = self
}) })
menu.addItem(NSMenuItem(title: "Quit phpmon", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
// Update the menu item on the main thread
DispatchQueue.main.async { DispatchQueue.main.async {
self.statusItem.menu = menu self.statusItem.menu = menu
} }
} }
} }
/**
Sets the status bar image based on a version string.
*/
func setStatusBarImage(version: String) { func setStatusBarImage(version: String) {
self.setStatusBar( self.setStatusBar(
image: MenuBarImageGenerator.textToImage( image: MenuBarImageGenerator.textToImage(text: version)
text: version
)
) )
} }
/**
Sets the status bar image, based on the provided NSImage.
The image will be used as a template image.
*/
func setStatusBar(image: NSImage) { func setStatusBar(image: NSImage) {
if let button = statusItem.button { if let button = statusItem.button {
image.isTemplate = true image.isTemplate = true
@ -93,12 +130,34 @@ class MainMenu: NSObject, NSWindowDelegate {
} }
} }
// MARK: - Callable via Obj-C (#selector) // MARK: - Nicer callbacks
@objc func openOutput() { /**
LogViewController.show(delegate: self) Executes a specific callback and fires the completion callback,
while updating the UI as required. As long as the completion callback
does not fire, the app is presumed to be busy and the UI reflects this.
- Parameter execute: Escaping callback of the work that needs to happen.
- Parameter completion: Callback that is fired when the work is done.
*/
private func waitAndExecute(_ execute: @escaping () -> Void, _ completion: @escaping () -> Void = {})
{
App.shared.busy = true
self.setBusyImage()
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
self.update()
execute()
App.shared.busy = false
DispatchQueue.main.async {
self.updatePhpVersionInStatusBar()
self.update()
completion()
}
}
} }
// MARK: - User Interface
@objc func updatePhpVersionInStatusBar() { @objc func updatePhpVersionInStatusBar() {
App.shared.currentVersion = PhpVersion() App.shared.currentVersion = PhpVersion()
if (App.shared.busy) { if (App.shared.busy) {
@ -113,17 +172,71 @@ class MainMenu: NSObject, NSWindowDelegate {
self.update() self.update()
} }
@objc public func openAbout() { @objc func setBusyImage() {
NSApplication.shared.activate(ignoringOtherApps: true) DispatchQueue.main.async {
NSApplication.shared.orderFrontStandardAboutPanel() self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
}
}
// MARK: - Actions
@objc public func restartPhpFpm() {
self.waitAndExecute({
Actions.restartPhpFpm()
})
}
@objc public func restartNginx() {
self.waitAndExecute({
Actions.restartNginx()
})
}
@objc public func toggleXdebug() {
self.waitAndExecute({
Actions.toggleXdebug()
})
}
@objc public func openPhpInfo() {
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
Shell.user.run("/usr/local/bin/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
NSWorkspace.shared.open(URL(string: "file:///private/tmp/phpmon_phpinfo.html")!)
}
@objc public func forceRestartLatestPhp() {
// Tell the user the switch is about to occur
_ = Alert.present(
messageText: "alert.force_reload.title".localized,
informativeText: "alert.force_reload.info".localized
)
// Start switching
self.waitAndExecute({ Actions.fixMyPhp() }, {
_ = Alert.present(
messageText: "alert.force_reload_done.title".localized,
informativeText: "alert.force_reload_done.info".localized
)
})
} }
@objc public func openActiveConfigFolder() { @objc public func openActiveConfigFolder() {
Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short) if (App.shared.currentVersion!.error) {
// php version was not identified
Actions.openGenericPhpConfigFolder()
} else {
// php version was identified
Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short)
}
}
@objc public func openValetConfigFolder() {
Actions.openValetConfigFolder()
} }
@objc public func switchToPhpVersion(sender: AnyObject) { @objc public func switchToPhpVersion(sender: AnyObject) {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!) self.setBusyImage()
// TODO: A wise man once said: using tags is not good. Fix this.
let index = sender.tag! let index = sender.tag!
let version = App.shared.availablePhpVersions[index] let version = App.shared.availablePhpVersions[index]
App.shared.busy = true App.shared.busy = true
@ -143,12 +256,21 @@ class MainMenu: NSObject, NSWindowDelegate {
DispatchQueue.main.async { DispatchQueue.main.async {
self.updatePhpVersionInStatusBar() self.updatePhpVersionInStatusBar()
self.update() self.update()
// Send a notification that the switch has been completed
LocalNotification.send(
title: String(format: "notification.version_changed_title".localized, version),
subtitle: String(format: "notification.version_changed_desc".localized, version)
)
} }
} }
} }
func windowWillClose(_ notification: Notification) { @objc public func openAbout() {
App.shared.windowController = nil NSApplication.shared.activate(ignoringOtherApps: true)
Shell.user.delegate = nil NSApplication.shared.orderFrontStandardAboutPanel()
}
@objc public func terminateApp() {
NSApplication.shared.terminate(nil)
} }
} }

View File

@ -1,6 +1,6 @@
// //
// Shell.swift // Shell.swift
// phpmon // PHP Monitor
// //
// Created by Nico Verbruggen on 11/06/2019. // Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved. // Copyright © 2019 Nico Verbruggen. All rights reserved.
@ -8,41 +8,33 @@
import Cocoa import Cocoa
protocol ShellDelegate: class {
func didCompleteCommand(historyItem: ShellHistoryItem)
}
class ShellHistoryItem {
var command: String
var output: String
var date: Date
init(command: String, output: String) {
self.command = command
self.output = output
self.date = Date()
}
}
class Shell { class Shell {
// Singleton to access a user shell (with --login) /**
Singleton to access a user shell (with --login)
*/
static let user = Shell() static let user = Shell()
var history : [ShellHistoryItem] = [] /**
Runs a shell command without using the output.
var delegate : ShellDelegate? Uses the default shell.
/// Runs a shell command without using the description. - Parameter command: The command to run
*/
public func run(_ command: String) { public func run(_ command: String) {
// Equivalent of piping to /dev/null; don't do anything with the string // Equivalent of piping to /dev/null; don't do anything with the string
_ = self.pipe(command) _ = self.pipe(command)
} }
/// Runs a shell command and returns the output. /**
public func pipe(_ command: String) -> String { Runs a shell command and returns the output.
- Parameter command: The command to run
- Parameter shell: Path to the shell to invoke
*/
public func pipe(_ command: String, shell: String = "/bin/sh") -> String {
let task = Process() let task = Process()
task.launchPath = "/bin/bash" task.launchPath = shell
task.arguments = ["--login", "-c", command] task.arguments = ["--login", "-c", command]
let pipe = Pipe() let pipe = Pipe()
@ -54,17 +46,8 @@ class Shell {
let output: String = NSString( let output: String = NSString(
data: data, data: data,
encoding: String.Encoding.utf8.rawValue encoding: String.Encoding.utf8.rawValue
)!.replacingOccurrences( )! as String
of: "\u{1B}(B\u{1B}[m",
with: ""
) as String
let historyItem = ShellHistoryItem(command: command, output: output)
history.append(historyItem)
// Keep the last 100 items
history = history.suffix(100)
delegate?.didCompleteCommand(historyItem: historyItem)
return output return output
} }
} }

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> <deployment identifier="macosx"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Application--> <!--Application-->
@ -11,18 +11,18 @@
<application id="hnw-xV-0zn" sceneMemberID="viewController"> <application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> <menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items> <items>
<menuItem title="phpmon" id="1Xt-HY-uBw"> <menuItem title="PHP Monitor" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="phpmon" systemMenu="apple" id="uQy-DD-JDr"> <menu key="submenu" title="PHP Monitor" systemMenu="apple" id="uQy-DD-JDr">
<items> <items>
<menuItem title="About phpmon" id="5kV-Vb-QxS"> <menuItem title="About PHP Monitor" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/> <action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/> <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Quit phpmon" keyEquivalent="q" id="4sb-4s-VLi"> <menuItem title="Quit PHP Monitor" keyEquivalent="q" id="4sb-4s-VLi">
<connections> <connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/> <action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections> </connections>
@ -34,7 +34,7 @@
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ"> <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items> <items>
<menuItem title="phpmon Help" keyEquivalent="?" id="FKE-Sm-Kum"> <menuItem title="PHP Monitor Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections> <connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/> <action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections> </connections>
@ -48,73 +48,11 @@
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/> <outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections> </connections>
</application> </application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="phpmon" customModuleProvider="target"/> <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="PHP_Monitor" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="134" y="40"/> <point key="canvasLocation" x="-343" y="-16"/>
</scene>
<!--Log View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController storyboardIdentifier="logWindow" id="XfG-lQ-9wD" customClass="LogViewController" customModule="phpmon" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" identifier="main" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="662" height="475"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ICa-gx-jgq">
<rect key="frame" x="573" y="8" width="75" height="32"/>
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="3md-FI-EWa">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="pressed:" target="XfG-lQ-9wD" id="fIC-Bz-vTK"/>
</connections>
</button>
<scrollView borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vZy-5S-021">
<rect key="frame" x="20" y="46" width="622" height="409"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="s5L-AU-0fw">
<rect key="frame" x="1" y="1" width="620" height="407"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" smartInsertDelete="YES" id="tN6-Y9-1pA">
<rect key="frame" x="0.0" y="0.0" width="620" height="407"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="620" height="407"/>
<size key="maxSize" width="620" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Kho-JF-NZJ">
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="qp7-7R-gTO">
<rect key="frame" x="605" y="1" width="16" height="407"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="vZy-5S-021" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" constant="20" id="K0k-oE-r37"/>
<constraint firstAttribute="trailing" secondItem="ICa-gx-jgq" secondAttribute="trailing" constant="20" id="LFS-0E-Ibw"/>
<constraint firstItem="vZy-5S-021" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" constant="20" id="Nec-oI-CjE"/>
<constraint firstAttribute="trailing" secondItem="vZy-5S-021" secondAttribute="trailing" constant="20" id="kBJ-O5-eYI"/>
<constraint firstAttribute="bottom" secondItem="ICa-gx-jgq" secondAttribute="bottom" constant="15" id="kYB-Fn-DSA"/>
<constraint firstItem="ICa-gx-jgq" firstAttribute="top" secondItem="vZy-5S-021" secondAttribute="bottom" constant="10" id="xdn-yU-LVb"/>
</constraints>
</view>
<connections>
<outlet property="textView" destination="tN6-Y9-1pA" id="z77-me-Od6"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-105" y="377.5"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@ -1,72 +0,0 @@
//
// ViewController.swift
// phpmon
//
// Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved.
//
import Cocoa
class LogViewController: NSViewController, ShellDelegate {
public static func show(delegate: NSWindowDelegate? = nil) {
if (App.shared.windowController == nil) {
let vc = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "logWindow") as! LogViewController
Shell.user.delegate = vc
let window = NSWindow(contentViewController: vc)
window.title = "Terminal Output"
window.delegate = delegate
App.shared.windowController = NSWindowController(window: window)
}
App.shared.windowController!.showWindow(self)
NSApp.activate(ignoringOtherApps: true)
}
@IBOutlet var textView: NSTextView!
public func appendHistoryItem(_ historyItem: ShellHistoryItem) {
self.append(
"""
======
@ \(historyItem.date.toString())
------
$ \(historyItem.command)
------
> \(historyItem.output)
"""
)
}
public func append(_ text : String) {
self.textView.textStorage?.append(
NSAttributedString(
string: text,
attributes: [
NSAttributedString.Key.font: NSFont(name: "Menlo", size: 12.0)!
]
)
)
self.textView.scrollToEndOfDocument(nil)
}
override func viewDidLoad() {
self.textView.isEditable = false
for entry in Shell.user.history {
self.appendHistoryItem(entry)
}
}
func didCompleteCommand(historyItem: ShellHistoryItem) {
self.appendHistoryItem(historyItem)
}
@IBAction func pressed(_ sender: Any) {
self.view.window?.windowController?.close()
}
deinit {
print("VC deallocated")
}
}