Commit | Line | Data |
---|---|---|
fd1ea018 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
4 | | Copyright CiviCRM LLC. All rights reserved. | | |
5 | | | | |
6 | | This work is published under the GNU AGPLv3 license with some | | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
9 | +--------------------------------------------------------------------+ | |
10 | */ | |
11 | ||
12 | /** | |
13 | * | |
14 | * @package CRM | |
15 | * @copyright CiviCRM LLC https://civicrm.org/licensing | |
16 | */ | |
7782deca TO |
17 | class CRM_Core_BAO_Translation extends CRM_Core_DAO_Translation implements \Civi\Test\HookInterface { |
18 | ||
19 | use CRM_Core_DynamicFKAccessTrait; | |
fd1ea018 TO |
20 | |
21 | /** | |
22 | * Get a list of valid statuses for translated-strings. | |
23 | * | |
24 | * @return string[] | |
25 | */ | |
26 | public static function getStatuses($context = NULL) { | |
27 | $options = [ | |
28 | ['id' => 1, 'name' => 'active', 'label' => ts('Active')], | |
29 | ['id' => 2, 'name' => 'draft', 'label' => ts('Draft')], | |
30 | ]; | |
31 | return self::formatPsuedoconstant($context, $options); | |
32 | } | |
33 | ||
34 | /** | |
35 | * Get a list of tables with translatable strings. | |
36 | * | |
37 | * @return string[] | |
38 | * Ex: ['civicrm_event' => 'civicrm_event'] | |
39 | */ | |
40 | public static function getEntityTables() { | |
41 | if (!isset(Civi::$statics[__CLASS__]['allTables'])) { | |
42 | $tables = array_keys(self::getTranslatedFields()); | |
43 | Civi::$statics[__CLASS__]['allTables'] = array_combine($tables, $tables); | |
44 | } | |
45 | return Civi::$statics[__CLASS__]['allTables']; | |
46 | } | |
47 | ||
48 | /** | |
49 | * Get a list of fields with translatable strings. | |
50 | * | |
51 | * @return string[] | |
52 | * Ex: ['title' => 'title', 'description' => 'description'] | |
53 | */ | |
54 | public static function getEntityFields() { | |
55 | if (!isset(Civi::$statics[__CLASS__]['allFields'])) { | |
56 | $allFields = []; | |
57 | foreach (self::getTranslatedFields() as $columns) { | |
58 | foreach ($columns as $column => $sqlExpr) { | |
59 | $allFields[$column] = $column; | |
60 | } | |
61 | } | |
62 | Civi::$statics[__CLASS__]['allFields'] = $allFields; | |
63 | } | |
64 | return Civi::$statics[__CLASS__]['allFields']; | |
65 | } | |
66 | ||
67 | /** | |
68 | * Given a constant list of of id/name/label options, convert to the | |
69 | * format required by pseudoconstant. | |
70 | * | |
71 | * @param string|NULL $context | |
72 | * @param array $options | |
73 | * List of options, each as a record of id+name+label. | |
74 | * Ex: [['id' => 123, 'name' => 'foo_bar', 'label' => 'Foo Bar']] | |
75 | * | |
76 | * @return array|false | |
77 | */ | |
78 | private static function formatPsuedoconstant($context, array $options) { | |
79 | // https://docs.civicrm.org/dev/en/latest/framework/pseudoconstant/#context | |
80 | $key = ($context === 'match') ? 'name' : 'id'; | |
81 | $value = ($context === 'validate') ? 'name' : 'label'; | |
82 | return array_combine(array_column($options, $key), array_column($options, $value)); | |
83 | } | |
84 | ||
85 | /** | |
86 | * @return array | |
87 | * List of data fields to translate, organized by table and column. | |
88 | * Omitted/unlisted fields are not translated. Any listed field may be translated. | |
89 | * Values should be TRUE. | |
90 | * Ex: $fields['civicrm_event']['summary'] = TRUE | |
91 | */ | |
92 | public static function getTranslatedFields() { | |
93 | $key = 'translatedFields'; | |
94 | $cache = Civi::cache('fields'); | |
95 | if (($r = $cache->get($key)) !== NULL) { | |
96 | return $r; | |
97 | } | |
98 | ||
99 | $f = []; | |
100 | \CRM_Utils_Hook::translateFields($f); | |
101 | ||
102 | // Future: Assimilate defaults originating in XML (incl extension-entities) | |
103 | // e.g. CRM_Core_I18n_SchemaStructure::columns() will grab core fields | |
104 | ||
105 | $cache->set($key, $f); | |
106 | return $f; | |
107 | } | |
108 | ||
7782deca TO |
109 | /** |
110 | * When manipulating strings via the `Translation` entity (APIv4), ensure that the references are well-formed. | |
111 | * | |
112 | * @param \Civi\Api4\Event\ValidateValuesEvent $e | |
113 | */ | |
114 | public static function self_civi_api4_validate(\Civi\Api4\Event\ValidateValuesEvent $e) { | |
115 | $statuses = self::getStatuses('validate'); | |
116 | $dataTypes = [CRM_Utils_Type::T_STRING, CRM_Utils_Type::T_TEXT, CRM_Utils_Type::T_LONGTEXT]; | |
9ac45bdf | 117 | $htmlTypes = ['Text', 'TextArea', 'RichTextEditor', '']; |
7782deca TO |
118 | |
119 | foreach ($e->records as $r => $record) { | |
120 | if (array_key_exists('status_id', $record) && !isset($statuses[$record['status_id']])) { | |
121 | $e->addError($r, 'status_id', 'invalid', ts('Invalid status')); | |
122 | } | |
123 | ||
124 | $entityIdFields = ['entity_table', 'entity_field', 'entity_id']; | |
125 | $entityIdCount = (empty($record['entity_table']) ? 0 : 1) | |
126 | + (empty($record['entity_field']) ? 0 : 1) | |
127 | + (empty($record['entity_id']) ? 0 : 1); | |
128 | if ($entityIdCount === 0) { | |
129 | continue; | |
130 | } | |
131 | elseif ($entityIdCount < 3) { | |
132 | $e->addError($r, $entityIdFields, 'full_entity', ts('Must specify all entity identification fields')); | |
133 | } | |
134 | ||
135 | $simpleName = '/^[a-zA-Z0-9_]+$/'; | |
136 | if (!preg_match($simpleName, $record['entity_table']) || !preg_match($simpleName, $record['entity_field']) || !is_numeric($record['entity_id'])) { | |
137 | $e->addError($r, $entityIdFields, 'malformed_entity', ts('Entity reference is malformed')); | |
138 | continue; | |
139 | } | |
140 | ||
141 | // Which fields support translation? | |
142 | // - One could follow the same path as "Multilingual". Use | |
143 | // $translatable = CRM_Core_I18n_SchemaStructure::columns(); | |
144 | // if (!isset($translatable[$record['entity_table']][$record['entity_field']])) { | |
145 | // - Or, since we don't need schema-changes, we could be more generous and allow all freeform text fields... | |
146 | ||
147 | $daoClass = CRM_Core_DAO_AllCoreTables::getClassForTable($record['entity_table']); | |
148 | if (!$daoClass) { | |
149 | $e->addError($r, 'entity_table', 'bad_table', ts('Entity reference specifies a non-existent or non-translatable table')); | |
150 | continue; | |
151 | } | |
152 | ||
153 | $dao = new $daoClass(); | |
154 | $dao->id = $record['entity_id']; | |
155 | ||
156 | $field = $dao->getFieldSpec($record['entity_field']); | |
157 | if (!$field || !in_array($field['type'] ?? '', $dataTypes) || !in_array($field['html']['type'] ?? '', $htmlTypes)) { | |
158 | $e->addError($r, 'entity_field', 'bad_field', ts('Entity reference specifies a non-existent or non-translatable field')); | |
159 | } | |
160 | if (!$dao->find()) { | |
161 | $e->addError($r, 'entity_id', 'nonexistent_id', ts('Entity does not exist')); | |
162 | } | |
163 | } | |
164 | } | |
165 | ||
fd1ea018 | 166 | } |