Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.97% covered (success)
96.97%
64 / 66
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CalendarEventQueryBuilder
96.92% covered (success)
96.92%
63 / 65
75.00% covered (warning)
75.00%
3 / 4
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tableExists
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
4.18
 buildCalendarQueryParts
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
9
 buildEventDetailsSql
100.00% covered (success)
100.00%
5 / 5
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\Dashboard\Application\Service\Query;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use PDO;
12use Throwable;
13
14/**
15 * Enterprise Query Builder for Calendar Event queries and relational joins.
16 *
17 * Provides shared SELECT/JOIN clauses and table existence checks across calendar
18 * widgets and interactive event detail modal services.
19 */
20final readonly class CalendarEventQueryBuilder
21{
22    /**
23     * CalendarEventQueryBuilder constructor.
24     *
25     * @param string $tablePrefix Database table prefix.
26     */
27    public function __construct(
28        private string $tablePrefix = 'a_'
29    ) {
30    }
31
32    /**
33     * Checks if a database table exists in the target database.
34     *
35     * @param PDO    $pdo       Active PDO handle.
36     * @param string $tableName Target table name.
37     * @return bool True if table exists, false otherwise.
38     */
39    public function tableExists(PDO $pdo, string $tableName): bool
40    {
41        try {
42            $driver = (string) $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
43            if ($driver === 'sqlite') {
44                $stmt = $pdo->prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = :t");
45                $stmt->execute([':t' => $tableName]);
46                return $stmt->fetchColumn() !== false;
47            }
48
49            $stmt = $pdo->query('SHOW TABLES LIKE ' . $pdo->quote($tableName));
50            return $stmt !== false && $stmt->fetchColumn() !== false;
51        } catch (Throwable) {
52            return false;
53        }
54    }
55
56    /**
57     * Builds SELECT and JOIN clauses for calendar queries including company, process, and subprocess relations.
58     *
59     * @param PDO  $pdo              Database connection.
60     * @param bool $includeAttendees Whether to include attendees payload column.
61     * @return array{0: string, 1: string} [selectSql, joinSql]
62     */
63    public function buildCalendarQueryParts(PDO $pdo, bool $includeAttendees = false): array
64    {
65        $compTable = $this->tablePrefix . 'mod_companies_records';
66        $contTable = $this->tablePrefix . 'mod_contacts_records';
67        $projTable = $this->tablePrefix . 'mod_projects_records';
68        $ctrTable = $this->tablePrefix . 'mod_contracts_records';
69        $tckTable = $this->tablePrefix . 'mod_tickets_records';
70        $tskTable = $this->tablePrefix . 'mod_project_tasks_records';
71        $stgTable = $this->tablePrefix . 'mod_project_stages_records';
72
73        $selects = [
74            "cal.`id`", "cal.`subject`", "cal.`start_date`", "cal.`end_date`",
75            "cal.`status`", "cal.`event_type`", "cal.`priority`", "cal.`company_id`",
76            "cal.`contact_id`", "cal.`project_id`", "cal.`contract_id`",
77            "cal.`ticket_id`", "cal.`task_id`", "cal.`description`", "cal.`location`",
78            "cal.`meeting_url`", "cal.`owner`",
79        ];
80
81        if ($includeAttendees) {
82            $selects[] = "cal.`attendees`";
83        }
84
85        $joins = [];
86
87        if ($this->tableExists($pdo, $compTable)) {
88            $selects[] = "comp.`name` AS `company_name`";
89            $joins[] = "LEFT JOIN `{$compTable}` comp ON comp.`id` = cal.`company_id`";
90        } else {
91            $selects[] = "NULL AS `company_name`";
92        }
93
94        if ($this->tableExists($pdo, $contTable)) {
95            $selects[] = "CONCAT_WS(' ', cont.`first_name`, cont.`last_name`) AS `contact_name`";
96            $joins[] = "LEFT JOIN `{$contTable}` cont ON cont.`id` = cal.`contact_id`";
97        } else {
98            $selects[] = "NULL AS `contact_name`";
99        }
100
101        $processExprs = [];
102        if ($this->tableExists($pdo, $projTable)) {
103            $processExprs[] = "proj.`project_name`";
104            $joins[] = "LEFT JOIN `{$projTable}` proj ON proj.`id` = cal.`project_id`";
105        }
106        if ($this->tableExists($pdo, $ctrTable)) {
107            $processExprs[] = "ctr.`contract_name`";
108            $joins[] = "LEFT JOIN `{$ctrTable}` ctr ON ctr.`id` = cal.`contract_id`";
109        }
110        $processExprs[] = "cal.`process_ref`";
111        $selects[] = "COALESCE(" . implode(', ', $processExprs) . ", '') AS `process_name`";
112
113        $subprocessExprs = [];
114        if ($this->tableExists($pdo, $tckTable)) {
115            $subprocessExprs[] = "tck.`subject`";
116            $joins[] = "LEFT JOIN `{$tckTable}` tck ON tck.`id` = cal.`ticket_id`";
117        }
118        if ($this->tableExists($pdo, $tskTable)) {
119            $subprocessExprs[] = "tsk.`task_name`";
120            $joins[] = "LEFT JOIN `{$tskTable}` tsk ON tsk.`id` = cal.`task_id`";
121        }
122        if ($this->tableExists($pdo, $stgTable)) {
123            $subprocessExprs[] = "stg.`stage_name`";
124            $joins[] = "LEFT JOIN `{$stgTable}` stg ON stg.`id` = cal.`stage_id`";
125        }
126        $subprocessExprs[] = "cal.`subprocess_ref`";
127        $selects[] = "COALESCE(" . implode(', ', $subprocessExprs) . ", '') AS `subprocess_name`";
128
129        return [
130            implode(",\n                       ", $selects),
131            implode("\n                ", $joins),
132        ];
133    }
134
135    /**
136     * Builds full SQL statement for fetching single event modal details.
137     *
138     * @param PDO    $pdo      Database connection.
139     * @param string $calTable Calendar table name.
140     * @return string Formatted SQL query.
141     */
142    public function buildEventDetailsSql(PDO $pdo, string $calTable): string
143    {
144        [$selectSql, $joinSql] = $this->buildCalendarQueryParts($pdo, true);
145
146        return "SELECT {$selectSql}
147                FROM   `{$calTable}` cal
148                {$joinSql}
149                WHERE  cal.`id` = :id
150                  AND  cal.`special_access` = 1
151                LIMIT  1";
152    }
153}