This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Wabel\Zoho\CRM\Copy; |
||
4 | |||
5 | use Doctrine\DBAL\Connection; |
||
6 | use Doctrine\DBAL\Schema\Schema; |
||
7 | use Doctrine\DBAL\Schema\SchemaDiff; |
||
8 | use Psr\Log\LoggerInterface; |
||
9 | use Psr\Log\NullLogger; |
||
10 | use Wabel\Zoho\CRM\AbstractZohoDao; |
||
11 | use function Stringy\create as s; |
||
12 | |||
13 | /** |
||
14 | * This class is in charge of synchronizing one table of your database with Zoho records. |
||
15 | */ |
||
16 | class ZohoDatabaseCopier |
||
17 | { |
||
18 | /** |
||
19 | * @var Connection |
||
20 | */ |
||
21 | private $connection; |
||
22 | |||
23 | private $prefix; |
||
24 | |||
25 | /** |
||
26 | * @var ZohoChangeListener[] |
||
27 | */ |
||
28 | private $listeners; |
||
29 | |||
30 | /** |
||
31 | * @var LoggerInterface |
||
32 | */ |
||
33 | private $logger; |
||
34 | |||
35 | /** |
||
36 | * @var LocalChangesTracker |
||
37 | */ |
||
38 | private $localChangesTracker; |
||
39 | |||
40 | /** |
||
41 | * ZohoDatabaseCopier constructor. |
||
42 | * |
||
43 | * @param Connection $connection |
||
44 | * @param string $prefix Prefix for the table name in DB |
||
45 | * @param ZohoChangeListener[] $listeners The list of listeners called when a record is inserted or updated. |
||
46 | */ |
||
47 | public function __construct(Connection $connection, $prefix = 'zoho_', array $listeners = [], LoggerInterface $logger = null) |
||
48 | { |
||
49 | $this->connection = $connection; |
||
50 | $this->prefix = $prefix; |
||
51 | $this->listeners = $listeners; |
||
52 | if ($logger === null) { |
||
53 | $this->logger = new NullLogger(); |
||
54 | } else { |
||
55 | $this->logger = $logger; |
||
56 | } |
||
57 | $this->localChangesTracker = new LocalChangesTracker($connection, $this->logger); |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * @param LoggerInterface $logger |
||
62 | */ |
||
63 | public function setLogger(LoggerInterface $logger) |
||
64 | { |
||
65 | $this->logger = $logger; |
||
66 | } |
||
67 | |||
68 | |||
69 | /** |
||
70 | * @param AbstractZohoDao $dao |
||
71 | * @param bool $incrementalSync Whether we synchronize only the modified files or everything. |
||
72 | */ |
||
73 | public function copy(AbstractZohoDao $dao, $incrementalSync = true, $twoWaysSync = true) |
||
74 | { |
||
75 | if ($twoWaysSync === true) { |
||
76 | $this->localChangesTracker->createTrackingTables(); |
||
77 | } |
||
78 | $this->synchronizeDbModel($dao, $twoWaysSync); |
||
79 | $this->copyData($dao, $incrementalSync, $twoWaysSync); |
||
80 | // TODO: we need to track DELETED records in Zoho! |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * Synchronizes the DB model with Zoho. |
||
85 | * |
||
86 | * @param AbstractZohoDao $dao |
||
87 | * @param bool $twoWaysSync |
||
88 | * @throws \Doctrine\DBAL\DBALException |
||
89 | * @throws \Doctrine\DBAL\Schema\SchemaException |
||
90 | */ |
||
91 | private function synchronizeDbModel(AbstractZohoDao $dao, $twoWaysSync) |
||
92 | { |
||
93 | $tableName = $this->getTableName($dao); |
||
94 | $this->logger->info("Synchronizing DB Model for ".$tableName); |
||
95 | |||
96 | $schema = new Schema(); |
||
97 | $table = $schema->createTable($tableName); |
||
98 | |||
99 | $flatFields = $this->getFlatFields($dao->getFields()); |
||
0 ignored issues
–
show
|
|||
100 | |||
101 | $table->addColumn('id', 'string', ['length' => 100]); |
||
102 | $table->setPrimaryKey(['id']); |
||
103 | |||
104 | foreach ($flatFields as $field) { |
||
105 | $columnName = $field['name']; |
||
106 | |||
107 | $length = null; |
||
108 | $index = false; |
||
109 | |||
110 | // Note: full list of types available here: https://www.zoho.com/crm/help/customization/custom-fields.html |
||
111 | switch ($field['type']) { |
||
112 | case 'Lookup ID': |
||
113 | case 'Lookup': |
||
114 | $type = 'string'; |
||
115 | $length = 100; |
||
116 | $index = true; |
||
117 | break; |
||
118 | case 'OwnerLookup': |
||
119 | $type = 'string'; |
||
120 | $index = true; |
||
121 | $length = 25; |
||
122 | break; |
||
123 | case 'Formula': |
||
124 | // Note: a Formula can return any type, but we have no way to know which type it returns... |
||
125 | $type = 'string'; |
||
126 | $length = 100; |
||
127 | break; |
||
128 | case 'DateTime': |
||
129 | $type = 'datetime'; |
||
130 | break; |
||
131 | case 'Date': |
||
132 | $type = 'date'; |
||
133 | break; |
||
134 | case 'DateTime': |
||
135 | $type = 'datetime'; |
||
136 | break; |
||
137 | case 'Boolean': |
||
138 | $type = 'boolean'; |
||
139 | break; |
||
140 | case 'TextArea': |
||
141 | $type = 'text'; |
||
142 | break; |
||
143 | case 'BigInt': |
||
144 | $type = 'bigint'; |
||
145 | break; |
||
146 | case 'Phone': |
||
147 | case 'Auto Number': |
||
148 | case 'Text': |
||
149 | case 'URL': |
||
150 | case 'Email': |
||
151 | case 'Website': |
||
152 | case 'Pick List': |
||
153 | case 'Multiselect Pick List': |
||
154 | $type = 'string'; |
||
155 | $length = $field['maxlength']; |
||
156 | break; |
||
157 | case 'Double': |
||
158 | case 'Percent': |
||
159 | $type = 'float'; |
||
160 | break; |
||
161 | case 'Integer': |
||
162 | $type = 'integer'; |
||
163 | break; |
||
164 | case 'Currency': |
||
165 | case 'Decimal': |
||
166 | $type = 'decimal'; |
||
167 | break; |
||
168 | default: |
||
169 | throw new \RuntimeException('Unknown type "'.$field['type'].'"'); |
||
170 | } |
||
171 | |||
172 | $options = []; |
||
173 | |||
174 | if ($length) { |
||
175 | $options['length'] = $length; |
||
176 | } |
||
177 | |||
178 | //$options['notnull'] = $field['req']; |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
75% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||
179 | $options['notnull'] = false; |
||
180 | |||
181 | $table->addColumn($columnName, $type, $options); |
||
182 | |||
183 | if ($index) { |
||
184 | $table->addIndex([$columnName]); |
||
185 | } |
||
186 | } |
||
187 | |||
188 | $dbalTableDiffService = new DbalTableDiffService($this->connection, $this->logger); |
||
189 | $hasChanges = $dbalTableDiffService->createOrUpdateTable($table); |
||
190 | |||
191 | if ($twoWaysSync && $hasChanges) { |
||
192 | $this->localChangesTracker->createInsertTrigger($table); |
||
193 | $this->localChangesTracker->createDeleteTrigger($table); |
||
194 | $this->localChangesTracker->createUpdateTrigger($table); |
||
195 | } |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * @param AbstractZohoDao $dao |
||
200 | * @param bool $incrementalSync Whether we synchronize only the modified files or everything. |
||
201 | * @param bool $twoWaysSync |
||
202 | * |
||
203 | * @throws \Doctrine\DBAL\DBALException |
||
204 | * @throws \Doctrine\DBAL\Schema\SchemaException |
||
205 | * @throws \Wabel\Zoho\CRM\Exception\ZohoCRMResponseException |
||
206 | */ |
||
207 | private function copyData(AbstractZohoDao $dao, $incrementalSync = true, $twoWaysSync = true) |
||
208 | { |
||
209 | $tableName = $this->getTableName($dao); |
||
210 | |||
211 | if ($incrementalSync) { |
||
212 | $this->logger->info("Copying incremental data for '$tableName'"); |
||
213 | // Let's get the last modification date: |
||
214 | $lastActivityTime = $this->connection->fetchColumn('SELECT MAX(lastActivityTime) FROM '.$tableName); |
||
215 | if ($lastActivityTime !== null) { |
||
216 | $lastActivityTime = new \DateTime($lastActivityTime); |
||
217 | $this->logger->info("Last activity time: ".$lastActivityTime->format('c')); |
||
218 | // Let's add one second to the last activity time (otherwise, we are fetching again the last record in DB). |
||
219 | $lastActivityTime->add(new \DateInterval("PT1S")); |
||
220 | } |
||
221 | |||
222 | $records = $dao->getRecords(null, null, $lastActivityTime); |
||
223 | $deletedRecordIds = $dao->getDeletedRecordIds($lastActivityTime); |
||
224 | } else { |
||
225 | $this->logger->notice("Copying FULL data for '$tableName'"); |
||
226 | $records = $dao->getRecords(); |
||
227 | $deletedRecordIds = []; |
||
228 | } |
||
229 | $this->logger->info("Fetched ".count($records)." records"); |
||
230 | |||
231 | $table = $this->connection->getSchemaManager()->createSchema()->getTable($tableName); |
||
232 | |||
233 | $flatFields = $this->getFlatFields($dao->getFields()); |
||
0 ignored issues
–
show
The method
getFields() cannot be called from this context as it is declared protected in class Wabel\Zoho\CRM\AbstractZohoDao .
This check looks for access to methods that are not accessible from the current context. If you need to make a method accessible to another context you can raise its visibility level in the defining class. ![]() |
|||
234 | $fieldsByName = []; |
||
235 | foreach ($flatFields as $field) { |
||
236 | $fieldsByName[$field['name']] = $field; |
||
237 | } |
||
238 | |||
239 | $select = $this->connection->prepare('SELECT * FROM '.$tableName.' WHERE id = :id'); |
||
240 | |||
241 | $this->connection->beginTransaction(); |
||
242 | |||
243 | foreach ($records as $record) { |
||
244 | $data = []; |
||
245 | $types = []; |
||
246 | foreach ($table->getColumns() as $column) { |
||
247 | if ($column->getName() === 'id') { |
||
248 | continue; |
||
249 | } else { |
||
250 | $field = $fieldsByName[$column->getName()]; |
||
251 | $getterName = $field['getter']; |
||
252 | $data[$column->getName()] = $record->$getterName(); |
||
253 | $types[$column->getName()] = $column->getType()->getName(); |
||
254 | } |
||
255 | } |
||
256 | |||
257 | $select->execute(['id' => $record->getZohoId()]); |
||
258 | $result = $select->fetch(\PDO::FETCH_ASSOC); |
||
259 | if ($result === false) { |
||
260 | $this->logger->debug("Inserting record with ID '".$record->getZohoId()."'."); |
||
261 | |||
262 | $data['id'] = $record->getZohoId(); |
||
263 | $types['id'] = 'string'; |
||
264 | |||
265 | $this->connection->insert($tableName, $data, $types); |
||
266 | |||
267 | foreach ($this->listeners as $listener) { |
||
268 | $listener->onInsert($data, $dao); |
||
269 | } |
||
270 | } else { |
||
271 | $this->logger->debug("Updating record with ID '".$record->getZohoId()."'."); |
||
272 | $identifier = ['id' => $record->getZohoId()]; |
||
273 | $types['id'] = 'string'; |
||
274 | |||
275 | $this->connection->update($tableName, $data, $identifier, $types); |
||
276 | |||
277 | // Let's add the id for the update trigger |
||
278 | $data['id'] = $record->getZohoId(); |
||
279 | foreach ($this->listeners as $listener) { |
||
280 | $listener->onUpdate($data, $result, $dao); |
||
281 | } |
||
282 | } |
||
283 | } |
||
284 | |||
285 | foreach ($deletedRecordIds as $id) { |
||
286 | $this->connection->delete($tableName, [ 'id' => $id ]); |
||
287 | if ($twoWaysSync) { |
||
288 | // TODO: we could detect if there are changes to be updated to the server and try to warn with a log message |
||
289 | // Also, let's remove the newly created field (because of the trigger) to avoid looping back to Zoho |
||
290 | $this->connection->delete('local_delete', [ 'table_name' => $tableName, 'id' => $id ]); |
||
291 | $this->connection->delete('local_update', [ 'table_name' => $tableName, 'id' => $id ]); |
||
292 | } |
||
293 | } |
||
294 | |||
295 | $this->connection->commit(); |
||
296 | } |
||
297 | |||
298 | private function getFlatFields(array $fields) |
||
299 | { |
||
300 | $flatFields = []; |
||
301 | foreach ($fields as $cat) { |
||
302 | $flatFields = array_merge($flatFields, $cat); |
||
303 | } |
||
304 | |||
305 | return $flatFields; |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Computes the name of the table based on the DAO plural module name. |
||
310 | * |
||
311 | * @param AbstractZohoDao $dao |
||
312 | * |
||
313 | * @return string |
||
314 | */ |
||
315 | private function getTableName(AbstractZohoDao $dao) |
||
316 | { |
||
317 | $tableName = $this->prefix.$dao->getPluralModuleName(); |
||
0 ignored issues
–
show
The method
getPluralModuleName() cannot be called from this context as it is declared protected in class Wabel\Zoho\CRM\AbstractZohoDao .
This check looks for access to methods that are not accessible from the current context. If you need to make a method accessible to another context you can raise its visibility level in the defining class. ![]() |
|||
318 | $tableName = s($tableName)->upperCamelize()->underscored(); |
||
319 | |||
320 | return (string) $tableName; |
||
321 | } |
||
322 | } |
||
323 |
This check looks for access to methods that are not accessible from the current context.
If you need to make a method accessible to another context you can raise its visibility level in the defining class.