eda8a608e6f6f31f550b8ad5dd2a9e15d2118b41
[civicrm-core.git] /
1 <?php
2
3 define('CCRM_EXTVALID_XMLFILE_FIELD', 'field_extension_release_xml');
4
5 /**
6 * Loads and Validates fields from the XML info file:
7 * Release version == <version> (required)
8 * Release Date == <releaseDate>
9 * Release Status == <develStage> (required)
10 * Release CiviCRM Compatibility == <compatibility><ver> (required)(multiple ok)
11 * Parent Extension FQN == <extension key="xx.yy.zz"> (required)
12 * Documentation Link == <url desc="http://example.com">
13 * (Postponed) Download URL > downloadUrl (required)
14 * TODO: Fully validate the XML document format against a DOCtype
15 */
16
17 /**
18 *
19 * implements hook_node_validate
20 */
21 function ccrm_extensionvalidation_node_validate($node, $form, &$form_state) {
22 // Make sure author of extension is in maintainers
23 if ('extension' == $node->type) {
24 $authornid = $node->uid; $delta = 0; $foundauthor = FALSE;
25 foreach ($form_state['values']['field_maintainers'][LANGUAGE_NONE] as $index => $ref) {
26 if (is_numeric($ref['target_id'])
27 && $form_state['values']['field_maintainers'][LANGUAGE_NONE][$index]['target_id'] == $authornid) {
28 $foundauthor = TRUE;
29 }
30 $delta++;
31 }
32 if (FALSE == $foundauthor) {
33 $form_state['values']['field_maintainers'][LANGUAGE_NONE][$delta]['target_id'] = $authornid;
34 $form_state['values']['field_maintainers'][LANGUAGE_NONE][$delta]['_weight'] = $delta;
35 }
36 }
37 // Set maintainers on releases from parent extension
38 if ($node->type == 'extension_release_civicrm') {
39 $parentnid = $node->field_extension_nr_crm[LANGUAGE_NONE][0]['nid'];
40 }
41 if ($node->type == 'extension_release_cms') {
42 $parentnid = $node->field_extension_nr_cms[LANGUAGE_NONE][0]['nid'];
43 }
44 if ($node->type == 'extension_release_civicrm' || $node->type == 'extension_release_cms') {
45 $parent_node = is_numeric($parentnid) ? node_load($parentnid) : NULL;
46 if (NULL == $parent_node) {
47 _ccrmextvalid_formseterror('title', t('Could not determine parent extension. Please add via extension page.'));
48 return;
49 }
50 }
51 if ($node->type == 'extension_release_cms') {
52 // Delete current maintainers
53 foreach ($form_state['values']['field_extension_release_na_mains'][LANGUAGE_NONE] as $index => $ref) {
54 if (is_numeric($ref['target_id'])) {
55 $form_state['values']['field_extension_release_na_mains'][LANGUAGE_NONE][$index]['target_id'] = NULL;
56 }
57 }
58 // Check parent node has maintainers set and add maintainers from parent
59 $delta = 0;
60 foreach ($parent_node->field_maintainers[LANGUAGE_NONE] as $index => $ref) {
61 if (is_numeric($ref['target_id'])) {
62 $form_state['values']['field_extension_release_na_mains'][LANGUAGE_NONE][$index]['target_id'] = $ref['target_id'];
63 $form_state['values']['field_extension_release_na_mains'][LANGUAGE_NONE][$index]['_weight'] = $delta++;
64 }
65 }
66 }
67 if ($node->type == 'extension_release_civicrm') {
68 // Delete current maintainers
69 foreach ($form_state['values']['field_extension_release_ci_mains'][LANGUAGE_NONE] as $index => $ref) {
70 if (is_numeric($ref['target_id'])) {
71 $form_state['values']['field_extension_release_ci_mains'][LANGUAGE_NONE][$index]['target_id'] = NULL;
72 }
73 }
74 // Check parent node has maintainers set and add maintainers from parent
75 $delta = 0;
76 foreach ($parent_node->field_maintainers[LANGUAGE_NONE] as $index => $ref) {
77 if (is_numeric($ref['target_id'])) {
78 $form_state['values']['field_extension_release_ci_mains'][LANGUAGE_NONE][$index]['target_id'] = $ref['target_id'];
79 $form_state['values']['field_extension_release_ci_mains'][LANGUAGE_NONE][$index]['_weight'] = $delta++;
80 }
81 }
82 }
83 if ($node->type == 'extension_release_civicrm') {
84 _ccrmextvalid_validatexml($node, $form, &$form_state, $parent_node);
85 }
86 }
87
88 function _ccrmextvalid_validatexml($node, $form, &$form_state, $parent_node) {
89 $keeperrors = array();
90 $file_id = $form_state['values'][CCRM_EXTVALID_XMLFILE_FIELD][LANGUAGE_NONE][0]['fid'];
91
92 if ($file_id != 0) {
93 $file = file_load($file_id);
94 if ($file) {
95 libxml_use_internal_errors(TRUE);
96 $doc = new DOMDocument();
97 $doc->recover = TRUE;
98 $doc->strictErrorChecking = FALSE;
99
100 if ($doc->load(drupal_realpath($file->uri))) {
101 // Initialize variables to be read
102 $got = array('version' => FALSE, 'releaseDate' => FALSE, 'develStage' => FALSE,
103 'ver' => FALSE, 'extension' => FALSE, 'documentation' => FALSE); //'downloadUrl' =>FALSE (postponed)
104 foreach ($got as $tag => $tagcontents) {
105 $wegotit = _ccrmextvalid_validateTag($node, $form, $form_state, $doc, $tag, $tagcontents, $keeperrors, $parent_node);
106 $got[$tag] = $tagcontents;
107 if ($wegotit) {
108 _ccrmextvalid_assignTag($form, &$form_state, $tag, &$tagcontents);
109 }
110 else {
111 if ($tag == 'extension') {
112 _ccrmextvalid_seterror('title', t("The Fully Qualified Name in the XML file did not match parent extension: @tagcont",
113 array('@tagcont' => $tagcontents)), $keeperrors);
114 }
115 else {
116 _ccrmextvalid_seterror('title', t("The Extension Release XML file contained an invalid or empty value for tag '@tag'",
117 array('@tag' => $tag)), $keeperrors);
118 }
119 }
120 }
121 }
122 else {
123 _ccrmextvalid_seterror( 'title', t('The XML file could not be read'), $keeperrors);
124 }
125 }
126 }
127 else {
128 _ccrmextvalid_seterror( CCRM_EXTVALID_XMLFILE_FIELD, t('Please upload an XML file descriptor'), $keeperrors);
129 }
130 if (!empty($keeperrors)) {
131 _ccrmextvalid_formseterror($keeperrors);
132 }
133 }
134
135 // return whether we had a
136 function _ccrmextvalid_validateTag($node, $form, &$form_state, $doc, $tag, &$tagcontents, &$keeperrors, $parent_node) {
137 $gotit = FALSE; $tagcount = 0; $contents = '';
138 switch ($tag) {
139 case 'version':
140 case 'releaseDate':
141 case 'develStage':
142 case 'downloadUrl':
143 $tagcount = _ccrmextvalid_getOneReqdByTag($doc, $tag, &$tagcontents);
144 break;
145 case 'ver':
146 $tagcount = _ccrmextvalid_getByTag($doc, $tag, &$tagcontents);
147 break;
148 case 'documentation':
149 $domnodes = $doc->getElementsByTagName('url');
150 $tagcount = $domnodes->length;
151 foreach ($domnodes as $node) {
152 if ($node->hasAttributes()) {
153 $attrib = $node->attributes->getNamedItem('desc');
154 if (strtolower($attrib->value) == "documentation") {
155 $tagcontents = $node->nodeValue;
156 }
157 }
158 }
159 break;
160 case 'extension':
161 $domnodes = $doc->getElementsByTagName('extension');
162 $tagcount = $domnodes->length;
163 if ($tagcount == 1) {
164 $elmt = $domnodes->item(0);
165 if ($elmt->hasAttributes()) {
166 $tagcontents = $elmt->getAttribute('key');
167 }
168 }
169 break;
170 default:
171 drupal_set_message(t('Invalid tag used in _ccrmextvalid_validateTag: @tag', array('@tag' => $tag)));
172 return FALSE;
173 }
174 if ($tagcount > 0) {
175 switch ($tag) {
176 case 'version':
177 module_load_include('version.inc', 'ccrm_extensionvalidation');
178 if (ccrm_extensionvalidation_version_isValid($tagcontents)) {
179 $contents = $tagcontents;
180 $gotit = TRUE;
181 }
182 break;
183 case 'releaseDate':
184 $tmp = filter_var($tagcontents, FILTER_SANITIZE_STRING);
185 if (preg_match('/\d{4}-\d{2}-\d{2}/', $tmp)) {
186 //$contents = new DateObject($tagcontents, NULL);
187 $contents = $tmp;
188 $gotit = TRUE;
189 }
190 break;
191 case 'develStage':
192 $contents = _ccrmextvalid_checkallowedval($tagcontents, 'field_extension_release_status');
193 if ($contents && (count($contents) == 1)) {
194 $gotit = TRUE;
195 }
196 $contents = $contents[0];
197 break;
198 case 'ver':
199 if ($tagcount == 1) {
200 $tagcontents = array($tagcontents);
201 }
202 $vers = array_map('_ccrmextvalid_mapversionval', $tagcontents);
203 $okvers = _ccrmextvalid_checkallowedval($vers, 'field_extension_release_civicrm');
204 if (count($okvers) == count($vers)) {
205 $gotit = TRUE;
206 $contents = array_map('_ccrmextvalid_mapversionvaltid', $okvers);
207 }
208 break;
209 case 'documentation':
210 $contents = filter_var($tagcontents, FILTER_VALIDATE_URL);
211 $gotit = TRUE;
212 break;
213 case 'extension':
214 $parentkey = isset($parent_node) ? $parent_node->field_extension_fq_name[LANGUAGE_NONE][0]['safe_value'] : NULL;
215 $contents = $parentkey . ' vs. ' . $tagcontents;
216 if ($tagcontents == $parentkey) {
217 $gotit = TRUE;
218 }
219 break;
220 default:
221 return FALSE;
222 }
223 $tagcontents = $contents;
224 }
225 return $gotit;
226 }
227
228 function _ccrmextvalid_assignTag($form, &$form_state, $tag, &$tagcontents) {
229 // Add $tagcontents to $form_state
230 switch ($tag) {
231 case 'version':
232 module_load_include('version.inc', 'ccrm_extensionvalidation');
233 $form_state['values']['field_extension_release_version'][LANGUAGE_NONE][0]['value'] = t('Version @ver', array('@ver' => $tagcontents));
234 $form_state['values']['field_extension_release_vc'][LANGUAGE_NONE][0]['value'] = ccrm_extensionvalidation_version_normalize($tagcontents);
235 break;
236 case 'releaseDate':
237 $form_state['values']['field_extension_release_date'][LANGUAGE_NONE][0]['value'] = $tagcontents;
238 break;
239 case 'develStage':
240 $form_state['values']['field_extension_release_status'][LANGUAGE_NONE][0]['value'] = $tagcontents;
241 break;
242 case 'downloadUrl':
243 $form_state['values']['field_extension_release_url'][LANGUAGE_NONE][0]['url'] = $tagcontents;
244 break;
245 case 'ver':
246 $form_state['values']['field_extension_release_civicrm'][LANGUAGE_NONE] = $tagcontents;
247 break;
248 case 'documentation':
249 $form_state['values']['field_documentation'][LANGUAGE_NONE][0]['url'] = $tagcontents;
250 break;
251 }
252 }
253
254 // Get the value of a single tag from the XML that should only have one value
255 function _ccrmextvalid_getOneReqdByTag($doc, $tagname, &$values) {
256 $defaultval = $values;
257 $tagcount = _ccrmextvalid_getByTag($doc, $tagname, &$values);
258 if ($tagcount > 1) {
259 form_set_error(CCRM_EXTVALID_XMLFILE_FIELD,
260 t("XML file must contain only one '@tagname' element"), array('tagname' => $tagname));
261 }
262 return $tagcount;
263 }
264
265 // Get all tags by value from the XML
266 function _ccrmextvalid_getByTag($doc, $tagname, &$values) {
267 $domnodes = $doc->getElementsByTagName($tagname);
268 $nodecount = $domnodes->length;
269 if ($nodecount > 0) {
270 if ($nodecount > 1) {
271 $values = array();
272 for ($i = 0; $i < $domnodes->length; $i++) {
273 $values[] = $domnodes->item($i)->nodeValue;
274 }
275 }
276 else {
277 $values = $domnodes->item(0)->nodeValue;
278 }
279 }
280 return $nodecount;
281
282 }
283
284 // Compare a value with the allowed values (lower case)
285 function _ccrmextvalid_checkallowedval($vals, $fieldname) {
286 $retvals = array();
287 $vals = !is_array($vals) ? array($vals) : $vals;
288 $fld = field_info_field($fieldname);
289 if ($fld['type'] == 'taxonomy_term_reference') {
290 $allowedvals1 = taxonomy_allowed_values($fld);
291 }
292 elseif ($fld['type'] == 'list_text') {
293 $allowedvals1 = list_allowed_values(field_info_field($fieldname));
294 }
295 else {
296 // No other field types handled
297 return $retvals;
298 }
299 // Taxonomy tags search in values, list search in keys
300 $allowedvals = array_map('strtolower', $allowedvals1);
301 foreach ($vals as $key => $val) {
302 if ($fld['type'] == 'list_text') {
303 if (array_key_exists($val, $allowedvals)) {
304 $retvals[] = $val;
305 }
306 }
307 else {
308 $retvals = array_merge($retvals, array_keys($allowedvals, $val));
309 }
310 }
311 return $retvals;
312 }
313
314 // Little helper function for array_map
315 function _ccrmextvalid_mapversionval($val) {
316 return "civicrm {$val}";
317 }
318
319 // Little helper function for array_map
320 function _ccrmextvalid_mapversionvaltid($val) {
321 return array( 'tid' => $val);
322 }
323
324 function _ccrmextvalid_seterror( $name, $message, &$keeperrors) {
325 // Form_set_error can only happen once on an element so collect the errors
326 $keeperrors[$name] = empty($keeperrors[$name]) ? $message : $keeperrors[$name] . " " . $message;
327 }
328
329 function _ccrmextvalid_formseterror($errors) {
330 foreach ($errors as $name => $err) {
331 // No other field is working reliably, tried the file upload button & others so usually use title
332 form_set_error(check_plain($name), check_plain($err));
333 }
334 }