35 protected $licenseDao;
54 'text' => array(
'text',
'Text'),
55 'is_active' => array(
'is_active',
'Is Active',
'active'),
56 'created_by' => array(
'created_by',
'Created By',
'user_name'),
57 'group' => array(
'group',
'Group',
'group_name'),
58 'license_shortname' => array(
'license_shortname',
'License Shortname'),
59 'removing' => array(
'removing',
'Removing'),
60 'comment' => array(
'comment',
'Comment'),
61 'reportinfo' => array(
'reportinfo',
'License Text'),
62 'acknowledgement' => array(
'acknowledgement',
'Acknowledgement'),
64 'licenses_to_add' => array(
'licenses_to_add',
'Licenses To Add',
'add_licenses'),
65 'licenses_to_remove' => array(
'licenses_to_remove',
'Licenses To Remove',
'remove_licenses')
77 $this->licenseDao = $licenseDao ?: $GLOBALS[
'container']->get(
'dao.license');
107 if ($fileExtension ===
'json') {
121 $this->unescapeNewlines =
false;
123 $content = file_get_contents($filename);
124 if ($content ===
false) {
125 return _(
"Could not read JSON file");
128 $data = json_decode($content,
true);
129 if (json_last_error() !== JSON_ERROR_NONE) {
130 return _(
"Invalid JSON format: ") . json_last_error_msg();
133 if (!is_array($data)) {
134 return _(
"JSON file must contain an array of phrases");
147 $this->unescapeNewlines =
true;
149 $handle = fopen($filename,
'r');
150 if ($handle ===
false) {
151 return _(
"Could not open CSV file");
154 $this->headrow = fgetcsv($handle, 0, $this->delimiter, $this->enclosure);
155 if ($this->headrow ===
false) {
157 return _(
"Could not read CSV header");
161 $bom = chr(0xEF) . chr(0xBB) . chr(0xBF);
162 if (isset($this->headrow[0]) && strpos($this->headrow[0], $bom) === 0) {
163 $this->headrow[0] = substr($this->headrow[0], 3);
168 while (($row = fgetcsv($handle, 0, $this->delimiter, $this->enclosure)) !==
false) {
170 if (count($row) !== count($this->headrow)) {
172 return sprintf(_(
"CSV line %d has %d columns, expected %d"),
173 $lineNumber, count($row), count($this->headrow));
176 $data[] = array_combine($this->headrow, $row);
195 foreach ($data as $index => $phraseData) {
198 if ($result[
'success']) {
199 if (!empty($result[
'unchanged'])) {
201 } elseif (!empty($result[
'existing'])) {
207 $errors[] = sprintf(_(
"Row %d: %s"), $index + 1, $result[
'message']);
209 }
catch (\Throwable $e) {
210 $errors[] = sprintf(_(
"Row %d: %s"), $index + 1, $e->getMessage());
216 $parts[] = sprintf(_(
"%d phrase(s) created"), $created);
219 $parts[] = sprintf(_(
"%d license(s) added to existing phrase(s)"), $updated);
221 if ($unchanged > 0) {
222 $parts[] = sprintf(_(
"%d row(s) already up to date"), $unchanged);
224 $message = _(
"Import complete") .
": " . (empty($parts) ? _(
"nothing new to import") : implode(
', ', $parts));
225 if (!empty($errors)) {
226 $message .=
"\n" . _(
"Errors:") .
"\n" . implode(
"\n", $errors);
243 if (empty($mappedData[
'text'])) {
244 return array(
'success' =>
false,
'message' => _(
"Text is required"));
250 $textMd5 = md5($mappedData[
'text']);
256 $insertSql =
"INSERT INTO custom_phrase (text, text_md5, user_fk, group_fk, is_active)
257 VALUES ($1, $2, $3, $4, $5)
258 ON CONFLICT (text_md5) DO NOTHING
265 $this->
parseBoolean($mappedData[
'is_active'] ??
false) ?
'true' :
'false'
267 $row = $this->
dbManager->getSingleRow($insertSql, $params, __METHOD__ .
'.insertPhrase');
269 $isNewPhrase = ($row !==
false);
271 $cpPk = intval($row[
'cp_pk']);
273 $existing = $this->
dbManager->getSingleRow(
274 "SELECT cp_pk FROM custom_phrase WHERE text_md5 = $1", array($textMd5),
275 __METHOD__ .
'.findExisting');
276 if ($existing ===
false) {
277 throw new Exception(
"custom_phrase insert conflicted but no row found for text_md5");
279 $cpPk = intval($existing[
'cp_pk']);
283 $allFailed = array();
285 if (!empty($mappedData[
'licenses'])) {
287 $totalInserted += $result[
'inserted'];
288 $allFailed = array_merge($allFailed, $result[
'failed']);
289 } elseif ($isNewPhrase) {
291 if (!empty($mappedData[
'licenses_to_add'])) {
293 $totalInserted += $r[
'inserted'];
294 $allFailed = array_merge($allFailed, $r[
'failed']);
296 if (!empty($mappedData[
'licenses_to_remove'])) {
298 $totalInserted += $r[
'inserted'];
299 $allFailed = array_merge($allFailed, $r[
'failed']);
308 if ($totalInserted > 0) {
309 return array(
'success' =>
true,
'existing' =>
true,
310 'message' => sprintf(_(
"Added %d license(s) to existing phrase"), $totalInserted));
312 if (!empty($allFailed)) {
313 return array(
'success' =>
false,
314 'message' => sprintf(_(
"Duplicate text; could not associate license(s): %s"), implode(
', ', $allFailed)));
316 return array(
'success' =>
true,
'unchanged' =>
true,
317 'message' => _(
"Phrase already exists, nothing new to add"));
320 $message = _(
"Phrase imported successfully");
321 if (!empty($allFailed)) {
322 $message .=
". " . sprintf(_(
"Warning: Could not find license(s): %s"), implode(
', ', $allFailed));
324 if ($totalInserted > 0) {
325 $message .=
". " . sprintf(_(
"Associated %d license(s)"), $totalInserted);
327 return array(
'success' =>
true,
'message' => $message);
328 }
catch (\Throwable $e) {
330 return array(
'success' =>
false,
'message' => _(
"Failed to import phrase: ") . $e->getMessage());
343 if (isset($data[
'licenses']) && is_array($data[
'licenses'])) {
344 $mapped[
'licenses'] = $data[
'licenses'];
347 foreach ($this->alias as $standardName => $aliases) {
348 foreach ($aliases as
$alias) {
349 if (isset($data[
$alias])) {
350 $mapped[$standardName] = $data[
$alias];
356 if (empty($mapped[
'licenses']) && !empty($mapped[
'license_shortname'])) {
357 $mapped[
'licenses'] = array(array(
358 'shortname' => $mapped[
'license_shortname'],
359 'removing' => $this->
parseBoolean($mapped[
'removing'] ??
false),
360 'comment' => $mapped[
'comment'] ??
'',
361 'reportinfo' => $mapped[
'reportinfo'] ??
'',
362 'acknowledgement' => $mapped[
'acknowledgement'] ??
''
368 if (isset($mapped[
'text']) && !is_string($mapped[
'text'])) {
369 unset($mapped[
'text']);
371 if (!empty($mapped[
'licenses']) && is_array($mapped[
'licenses'])) {
372 $mapped[
'licenses'] = array_values(array_filter($mapped[
'licenses'],
function ($entry) {
373 return is_array($entry) && is_string($entry[
'shortname'] ??
null);
375 foreach ($mapped[
'licenses'] as &$licenseEntry) {
376 foreach (array(
'comment',
'reportinfo',
'acknowledgement') as $field) {
377 if (isset($licenseEntry[$field]) && !is_string($licenseEntry[$field])) {
378 $licenseEntry[$field] =
'';
382 unset($licenseEntry);
385 if ($this->unescapeNewlines) {
386 if (isset($mapped[
'text']) && is_string($mapped[
'text'])) {
389 if (!empty($mapped[
'licenses']) && is_array($mapped[
'licenses'])) {
390 foreach ($mapped[
'licenses'] as &$licenseEntry) {
391 foreach (array(
'comment',
'reportinfo',
'acknowledgement') as $field) {
392 if (isset($licenseEntry[$field]) && is_string($licenseEntry[$field])) {
397 unset($licenseEntry);
411 if (is_bool($value)) {
414 if (!is_scalar($value)) {
418 $value = strtolower(
trim($value));
419 return in_array($value, array(
'true',
'1',
'yes',
'on',
'active'));
442 foreach ($licenses as $entry) {
443 $shortname =
trim($entry[
'shortname'] ??
'');
444 if ($shortname ===
'') {
448 $license = $this->licenseDao->getLicenseByShortName($shortname, $groupId);
450 $failed[] = $shortname .
" (unknown)";
454 $licenseId = $license->getId();
455 $removing = $this->
parseBoolean($entry[
'removing'] ??
false);
459 $existing = $this->
dbManager->getSingleRow(
460 "SELECT 1 FROM custom_phrase_license_map WHERE cp_fk = $1 AND rf_fk = $2 LIMIT 1",
461 array($cpPk, $licenseId), __METHOD__ .
'.checkMapping');
469 'rf_fk' => $licenseId,
470 'removing' => $removing ?
'true' :
'false',
471 'comment' => ($entry[
'comment'] ??
'') ?:
null,
472 'reportinfo' => ($entry[
'reportinfo'] ??
'') ?:
null,
473 'acknowledgement' => ($entry[
'acknowledgement'] ??
'') ?:
null
477 $this->
dbManager->insertTableRow(
'custom_phrase_license_map', $insertData);
479 }
catch (\Throwable $e) {
480 $failed[] = $shortname .
" (insert failed)";
484 return array(
'inserted' => $inserted,
'skipped' => $skipped,
'failed' => $failed);
499 $names = is_array($licenseNames) ? $licenseNames : $this->
splitLicenseNames($licenseNames);
502 foreach ($names as $name) {
508 'shortname' => $name,
509 'removing' => $removing,
512 'acknowledgement' =>
''
517 return array(
'inserted' => $result[
'inserted'],
'failed' => $result[
'failed']);
527 foreach (array(
', ',
',',
';',
'|') as $separator) {
528 if (strpos($licenseNames, $separator) !==
false) {
529 return array_map(
'trim', explode($separator, $licenseNames));
532 return array($licenseNames);
543 $this->unescapeNewlines =
false;
static unescapeNewlines($value)
Reverse escapeNewlines(): literal becomes a real newline, any other escaped character is unescaped ...
Import custom text phrases from CSV/JSON.
importSinglePhrase($phraseData)
Import a single phrase.
importJsonData($data, string &$msg)
Import JSON data directly.
parseBoolean($value)
Parse boolean value from string.
setEnclosure($enclosure='"')
Update the enclosure.
setDelimiter($delimiter=',')
Update the delimiter.
associateLicenseNames($cpPk, $licenseNames, $removing, $groupId=null)
Legacy flat-format helper: split a license-name string/array and delegate to associateLicensesWithMet...
splitLicenseNames($licenseNames)
Split a delimited license-name string on the first separator found.
handleFile($filename, $fileExtension)
Read the CSV/JSON file and import it.
mapHeaders($data)
Map CSV headers to standard field names.
__construct(DbManager $dbManager, UserDao $userDao, LicenseDao $licenseDao=null)
handleCsvFile($filename)
Handle CSV file import.
associateLicensesWithMetadata($cpPk, $licenses, $groupId=null)
Associate licenses with a phrase, writing per-mapping metadata.
handleJsonFile($filename)
Handle JSON file import.
importPhrases($data)
Import phrases from data array.
Contains the constants and helpers for authentication of user.
static getUserId()
Get the current user's id.
static getGroupId()
Get the current user's group id.
char * trim(char *ptext)
Trimming whitespace.
fo_dbManager * dbManager
fo_dbManager object
Utility functions for specific applications.