Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
72 / 72
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
AppleMobileConfigGenerator
100.00% covered (success)
100.00%
71 / 71
100.00% covered (success)
100.00%
2 / 2
2
100.00% covered (success)
100.00%
1 / 1
 generate
100.00% covered (success)
100.00%
67 / 67
100.00% covered (success)
100.00%
1 / 1
1
 generateUuid
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Dav\Infrastructure\Config;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * Apple Mobile Configuration (.mobileconfig) Profile Generator.
13 *
14 * Generates an XML plist configuration profile for Apple iOS, iPadOS, and macOS
15 * containing native CalDAV and CardDAV account definitions.
16 *
17 * @package App\Modules\Dav\Infrastructure\Config
18 */
19final class AppleMobileConfigGenerator
20{
21    private const string DEFAULT_HOST = 'mail.ammonly.com';
22    private const int DEFAULT_PORT = 443;
23
24    /**
25     * Generates standard Apple .mobileconfig XML profile string.
26     *
27     * @param string $userEmail User email address / username.
28     * @param string $displayName Human-readable profile display name.
29     * @param string $host DAV server hostname (default mail.ammonly.com).
30     * @param int $port SSL port number (default 443).
31     * @return string Valid XML plist configuration profile.
32     */
33    public function generate(
34        string $userEmail,
35        string $displayName = 'Ammonly Groupware',
36        string $host = self::DEFAULT_HOST,
37        int $port = self::DEFAULT_PORT
38    ): string {
39        $profileUuid = $this->generateUuid();
40        $caldavUuid = $this->generateUuid();
41        $carddavUuid = $this->generateUuid();
42
43        $safeEmail = htmlspecialchars($userEmail, ENT_XML1, 'UTF-8');
44        $safeName = htmlspecialchars($displayName, ENT_XML1, 'UTF-8');
45        $safeHost = htmlspecialchars($host, ENT_XML1, 'UTF-8');
46        $principalUrl = sprintf('/SOGo/dav/%s/', $safeEmail);
47
48        $xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
49        $xml .= '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
50            . '"http://www.apple.com/DTDs/PropertyList-1.0.dtd">' . "\n";
51        $xml .= '<plist version="1.0">' . "\n";
52        $xml .= '<dict>' . "\n";
53        $xml .= "    <key>PayloadDisplayName</key>\n";
54        $xml .= "    <string>{$safeName}</string>\n";
55        $xml .= "    <key>PayloadIdentifier</key>\n";
56        $xml .= "    <string>com.ammonly.profile.{$profileUuid}</string>\n";
57        $xml .= "    <key>PayloadType</key>\n";
58        $xml .= "    <string>Configuration</string>\n";
59        $xml .= "    <key>PayloadUUID</key>\n";
60        $xml .= "    <string>{$profileUuid}</string>\n";
61        $xml .= "    <key>PayloadVersion</key>\n";
62        $xml .= "    <integer>1</integer>\n";
63        $xml .= "    <key>PayloadContent</key>\n";
64        $xml .= "    <array>\n";
65
66        // CalDAV Account
67        $xml .= "        <dict>\n";
68        $xml .= "            <key>PayloadType</key>\n";
69        $xml .= "            <string>com.apple.caldav.account</string>\n";
70        $xml .= "            <key>PayloadVersion</key>\n";
71        $xml .= "            <integer>1</integer>\n";
72        $xml .= "            <key>PayloadIdentifier</key>\n";
73        $xml .= "            <string>com.ammonly.caldav.{$caldavUuid}</string>\n";
74        $xml .= "            <key>PayloadUUID</key>\n";
75        $xml .= "            <string>{$caldavUuid}</string>\n";
76        $xml .= "            <key>CalDAVHostName</key>\n";
77        $xml .= "            <string>{$safeHost}</string>\n";
78        $xml .= "            <key>CalDAVPortNumber</key>\n";
79        $xml .= "            <integer>{$port}</integer>\n";
80        $xml .= "            <key>CalDAVUseSSL</key>\n";
81        $xml .= "            <true/>\n";
82        $xml .= "            <key>CalDAVPrincipalURL</key>\n";
83        $xml .= "            <string>{$principalUrl}</string>\n";
84        $xml .= "            <key>CalDAVAccountDescription</key>\n";
85        $xml .= "            <string>Ammonly Kalendarz</string>\n";
86        $xml .= "            <key>CalDAVUsername</key>\n";
87        $xml .= "            <string>{$safeEmail}</string>\n";
88        $xml .= "        </dict>\n";
89
90        // CardDAV Account
91        $xml .= "        <dict>\n";
92        $xml .= "            <key>PayloadType</key>\n";
93        $xml .= "            <string>com.apple.carddav.account</string>\n";
94        $xml .= "            <key>PayloadVersion</key>\n";
95        $xml .= "            <integer>1</integer>\n";
96        $xml .= "            <key>PayloadIdentifier</key>\n";
97        $xml .= "            <string>com.ammonly.carddav.{$carddavUuid}</string>\n";
98        $xml .= "            <key>PayloadUUID</key>\n";
99        $xml .= "            <string>{$carddavUuid}</string>\n";
100        $xml .= "            <key>CardDAVHostName</key>\n";
101        $xml .= "            <string>{$safeHost}</string>\n";
102        $xml .= "            <key>CardDAVPortNumber</key>\n";
103        $xml .= "            <integer>{$port}</integer>\n";
104        $xml .= "            <key>CardDAVUseSSL</key>\n";
105        $xml .= "            <true/>\n";
106        $xml .= "            <key>CardDAVPrincipalURL</key>\n";
107        $xml .= "            <string>{$principalUrl}</string>\n";
108        $xml .= "            <key>CardDAVAccountDescription</key>\n";
109        $xml .= "            <string>Ammonly Kontakty</string>\n";
110        $xml .= "            <key>CardDAVUsername</key>\n";
111        $xml .= "            <string>{$safeEmail}</string>\n";
112        $xml .= "        </dict>\n";
113
114        $xml .= "    </array>\n";
115        $xml .= "</dict>\n";
116        $xml .= "</plist>\n";
117
118        return $xml;
119    }
120
121    /**
122     * Generates a pseudo-random UUID v4 string.
123     */
124    private function generateUuid(): string
125    {
126        $data = random_bytes(16);
127        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
128        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
129
130        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
131    }
132}