4 * Project data importing script for project admin
6 * Copyright (c) 2011 Olivier Berger & Institut Telecom
8 * This program was developped in the frame of the COCLICO project
9 * (http://www.coclico-project.org/) with financial support of the Paris
12 * This file is part of FusionForge. FusionForge is free software;
13 * you can redistribute it and/or modify it under the terms of the
14 * GNU General Public License as published by the Free Software
15 * Foundation; either version 2 of the Licence, or (at your option)
18 * FusionForge is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 require_once '../../../www/env.inc.php';
29 require_once $gfwww.'include/pre.php';
31 require_once $gfwww.'include/role_utils.php';
33 //require_once $gfcommon.'valid/rule.class.php';
35 // don't include this in ProjectImporter, for unit test purposes, so do it here, in caller
36 require_once $gfcommon.'import/import_users.php';
37 //print_r($gfplugins.'projectimport/common/ProjectImporter.class.php');
39 require_once $gfplugins.'projectimport/common/ProjectImporter.class.php';
40 require_once $gfplugins.'projectimport/common/UploadedFiles.class.php';
42 // Dependency on php-arc
43 //include_once('arc/ARC2.php');
45 * Manages the display of the page : HTML + forms
47 * @author Olivier Berger
50 class ProjectImportPage extends FileManagerPage {
55 // will contain the list of spaces to be imported
56 protected $posted_spaces_imported;
58 // will contain mapping of imported users to forge users
59 protected $posted_user_mapping;
61 // will contain roles of users added to the project
62 protected $posted_new_member_roles;
64 protected $form_header_already_displayed;
67 function ProjectImportPage($HTML) {
69 $this->form_header_already_displayed = false;
70 $this->importer = False;
71 $this->posted_user_mapping = array();
72 $this->posted_new_member_roles = array();
73 $this->posted_spaces_imported = array();
74 $storage = new ProjectFilesDirectory($HTML, $group_id);
76 parent::FileManagerPage($HTML, $storage);
79 * Initializes data structures from POSTed data coming from the form input
81 function initialize_from_submitted_data() {
82 global $group_id, $feedback;
84 $group_id = getIntFromRequest('group_id');
86 $filechosen = $this->initialize_chosenfile_from_submitted();
88 // if a file was chose among the existing ones, try to import its JSON contents
90 //print_r($filechosen);
91 $json = fread(fopen($this->posted_selecteddumpfile, 'r'),filesize($this->posted_selecteddumpfile));
93 $this->importer = new ProjectImporter($group_id);
94 $this->importer->parse_OSLCCoreRDFJSON($json);
97 $triples = $importer->parse_OSLCCoreRDFJSON($json);
98 $ser = ARC2::getTurtleSerializer();
100 if(count($triples)) {
101 $message .= '<pre>'. nl2br(htmlspecialchars($ser->toTurtle($triples))) . '</pre>';
105 // Get the user mappings posted (if any)
106 if (getStringFromPost('submit_mappings')) {
107 foreach (array_keys($_POST) as $key) {
108 // print_r('key : '. $key);
109 if(!strncmp($key, 'map_', 4)) {
110 $imported_user = substr($key, 4);
111 $mapped_user = getStringFromPost($key);
112 // print_r('Mapped : '. $imported_user . ' to ' . $mapped_user);
115 $this->posted_user_mapping[$imported_user] = $mapped_user;
120 if (getStringFromPost('submit_new_roles')) {
121 foreach (array_keys($_POST) as $key) {
122 // print_r('key : '. $key);
123 if(!strncmp($key, 'role_', 5)) {
124 $added_user = substr($key, 5);
125 $new_role = getStringFromPost($key);
126 // print_r('Mapped : '. $imported_user . ' to ' . $mapped_user);
129 $this->posted_new_member_roles[$added_user] = $new_role;
134 // Get the spaces checked as to be imported (if any)
135 if (getStringFromPost('submit_spaces')) {
136 foreach (array_keys($_POST) as $key) {
137 if(!strncmp($key, 'import_', 7)) {
138 $spacesha1 = substr($key, 7);
139 // print_r('Selected for import : '. $spacesha1);
141 $this->posted_spaces_imported[] = $spacesha1;
145 // echo '$posted_spaces_imported : ';
146 // print_r($posted_spaces_imported);
150 if ((! $this->posted_selecteddumpfile) && (! $this->posted_uploadedfile)) {
151 $this->feedback(_('Please select an existing file to process, or upload a new one'));
156 * Display initial contents of the page
157 * @param string $message
159 function display_headers($message) {
160 global $group_id, $feedback;
163 $params['title']=_('Project importer');
164 $params['toptab']='projectimport';
165 $params['group']=$group_id;
167 site_project_header($params);
169 $this->message .= $message;
172 function display_users($imported_users)
176 $html .= $this->html_generator->boxTop(_("Users found in imported file"));
178 foreach($imported_users as $user => $userres) {
179 $html .= $this->importer->display_user($user);
182 $html .= $this->html_generator->boxBottom();
188 * Tries to match imported users to forge users, and display mapping form bits if needed
189 * @param array of ARC2 resources $imported_users
190 * @param boolean apply the changes
191 * @return html string
193 function match_users($imported_users, $apply = FALSE)
195 global $group_id, $feedback, $message;
200 $needs_to_warn = FALSE;
202 // if mapping has been provided for all imported users
203 $mapping_all_users_provided = TRUE;
205 // if all mapped users are already in the project
206 $all_mapped_users_in_project = TRUE;
208 // if all new project members roles posted by user
209 $all_new_project_members_roles_set = TRUE;
211 // array of existing forge users and the imported users that have been mapped to it
212 $new_member_map_users = array();
215 $role_names = array();
216 $group_object = group_get_object($group_id);
217 $existing_roles = $group_object->getRoles();
218 foreach ($existing_roles as $role) {
219 $name = $role->getName();
220 $role_names[$name] = & $role;
224 // Load users members of the project (may be needed later for display of user mapping selection widgets)
225 $existing_users = array();
226 $active_users = user_get_active_users();
227 foreach($active_users as $user_object) {
228 $username = $user_object->getUnixName();
229 //print_r('User : '.$username .'<br />');
231 if ($user_object->isMember($group_id)) {
232 //print_r('member of project as ');
233 $role = $user_object->getRole($group_id);
235 $role = $role->getName();
236 //print_r($role . '<br />');
240 print_r('dunno...<br />');
245 print_r('not member of project...<br />');
248 $existing_users[] = array( 'name' => $username,
252 // displays all imported users, with the found matching existing forge user, if any
253 foreach($imported_users as $user => $userres) {
255 $imported_username = $this->importer->get_user_name($user);
256 $imported_email = $this->importer->get_user_email($user);
258 $already_mapped = FALSE;
259 // check if the user already chose to map it
260 if (array_key_exists($imported_username, $this->posted_user_mapping)) {
261 $already_mapped = $this->posted_user_mapping[$imported_username];
262 $username = $this->posted_user_mapping[$imported_username];
265 // try to find user with same login
266 $username = $imported_username;
269 $automatically_matched = FALSE;
271 $user_object = user_get_object_by_name($username);
272 // if the user hasn't mapped it already, try some automatic mapping
273 if ( ! $already_mapped ) {
274 $mapping_all_users_provided = FALSE;
276 // if we have found an existing user with the same login, try to match it automatically
278 $automatically_matched = $username;
279 if ($this->message) {
280 $this->message .= '<br />';
282 $this->message .= sprintf(_('Found matching existing forge user with same login "%s"'), $username);
285 // try to match by email
286 $emails = array(strtolower($imported_email));
287 $user_objects = user_get_objects_by_email($emails);
288 if (count($user_objects) == 1) {
289 $user_object=$user_objects[0];
290 $username = $user_object->getUnixName();
291 $automatically_matched = $username;
292 if ($this->message) {
293 $this->message .= '<br />';
295 $this->message .= sprintf(_('Found matching existing forge user "%s" with same email "%s"'), $username, $imported_email);
300 if (! $user_object) {
301 $this->feedback(sprintf(_('Failed to find existing user matching imported user "%s"'), $username));
302 $needs_to_warn = TRUE;
305 // now construct mapping table to be displayed later
306 $html_tbody .= '<tr>';
307 $html_tbody .= '<td style="white-space: nowrap;">'. $imported_username .'</td>';
308 $html_tbody .= '<td style="white-space: nowrap;">'. $imported_email .'</td>';
309 $html_tbody .= '<td>'. $this->importer->get_user_role($user) . '</td>';
311 // if not all mapping of users has been provided, then must display selection widgets
312 if (! $mapping_all_users_provided ) {
314 $html_tbody .= '<td><select name="map_'.$imported_username.'">';
317 $html_tbody .= '<option value="0">'._('Optionally change for another existing user').'</option>';
320 $html_tbody .= '<option value="0" selected="selected">'._('Select existing user').'</option>';
322 // TODO : use html_build_select_box_from_arrays(...); ?
323 foreach($existing_users as $existing_user) {
324 $name = $existing_user['name'];
325 $role = $existing_user['role'];
327 $line = $name . ' (' . $role . ')';
329 $line = $name . ' ('. _('to be added to project') . ')';
331 if ( ($already_mapped && ($name == $already_mapped)) ||
332 ($automatically_matched && ($name == $automatically_matched)) ) {
333 $html_tbody .= '<option value="'. $name .'" selected="selected">'. $line.'</option>';
336 $html_tbody .= '<option value="'. $name .'">'. $line .'</option>';
339 $html_tbody .= '</select></td>';
341 else { // will display the mapped user anyway
342 $role = ' ('. _('need to add to project'). ')';
343 $user_object = user_get_object_by_name($already_mapped);
344 // if mapped user is already project member
345 if ($user_object->isMember($group_id)) {
346 $role = $user_object->getRole($group_id);
348 $role = ' ('. $role->getName() . ')';
352 // memorize the list of users that need to be added to the project
353 if (! array_key_exists($already_mapped, $new_member_map_users)) {
354 $new_member_map_users[$already_mapped] = array();
356 $new_member_map_users[$already_mapped][] = $imported_username;
357 $all_mapped_users_in_project = FALSE;
359 if ( ! array_key_exists($already_mapped, $this->posted_new_member_roles) ) {
360 $all_new_project_members_roles_set = FALSE;
363 $html_tbody .= '<td>'. $already_mapped . $role;
364 $html_tbody .= '<input type="hidden" name="map_'.$imported_username.'" value="'.$already_mapped.'" />';
365 $html_tbody .= '</td>';
367 $html_tbody .= '</tr>';
371 // OK, now, will be able to render the HTML
373 if (count($imported_users)) {
375 // If we have to provide the user with some dialog about mapping
376 if (! $mapping_all_users_provided) {
378 $html .= $this->display_users($imported_users);
380 if ($needs_to_warn) {
381 $html .= '<p>'._('Failed to find existing users matching some imported users.').'<br />'.
382 _('If you wish to map their data to existing users, choose them in the form bellow, and re-submit it:'). '</p>';
385 $html .= '<p>'._('You may change some mappings and re-submit.');
389 // display users mapping table
390 $html .= $this->html_generator->boxTop(_("Matching imported users to existing forge users"));
392 $html .= '<table width="100%"><thead><tr>';
393 $html .= '<th>'._('Imported user logname').'</th>';
394 $html .= '<th>'._('Imported user email').'</th>';
395 $html .= '<th>'._('Initial role').'</th>';
396 if (! $mapping_all_users_provided) {
397 $html .= '<th>'._('Map to existing user (role)').'</th>';
399 $html .= '<th>'._('Mapped to existing user').'</th>';
401 $html .= '</tr></thead><tbody>';
402 $html .= '<input type="hidden" name="submit_mappings" value="y" />';
404 $html .= $html_tbody;
406 $html .= '</tbody></table>';
407 $html .= $this->html_generator->boxBottom();
410 if ($mapping_all_users_provided) {
411 // the mapping must be applied as all users mapping has been posted
416 // now, need to check if new (mapped to) users need to be added to (roles of) the project
417 if ( ! $all_mapped_users_in_project ) {
419 // if the new project members haven't been posted by the user display box
420 if ( ! $all_new_project_members_roles_set ) {
422 $html .= $this->html_generator->boxTop(_("Matching new project members roles"));
424 $html .= '<table width="100%"><thead><tr>';
425 $html .= '<th>'._('New project member').'</th>';
426 $html .= '<th>'._('Imported users mapped to it').'</th>';
427 $html .= '<th>'._('New role').'</th>';
429 $html .= '</tr></thead><tbody>';
431 foreach($new_member_map_users as $new_member => $imported_users_mapped) {
433 $html .= '<td>'. $new_member . '</td>';
434 $html .= '<td>'. implode(', ', $imported_users_mapped) . '</td>';
436 // TODO : use a more sophisticated select box maybe : the selection by default of the first may not be the right thing to suggest ?
437 $html .= '<td>'. role_box($group_id, 'role_'.$new_member) . '</td>';
441 $html .= '<input type="hidden" name="submit_new_roles" value="y" />';
443 $html .= '</tbody></table>';
444 $html .= $this->html_generator->boxBottom();
448 // Last check if we can proceed to the user's import
450 foreach ($imported_users as $user => $userres) {
452 //print_r('Check for : '. $user. '<br />');
454 $imported_username = $this->importer->get_user_name($user);
455 $mapped_to_username = $this->posted_user_mapping[$imported_username];
456 $user_object = user_get_object_by_name($mapped_to_username);
459 if ( ! $user_object->isMember($group_id) ) {
460 // no need to add it, already in the group
461 // $this->message .= sprintf(_('Imported user "%s", mapped as "%s" which is already in the project : no need to add it.'), $imported_username, $mapped_to_username);
463 // need to add it to the group
464 if ( array_key_exists($mapped_to_username, $this->posted_new_member_roles) ) {
465 $role = $this->posted_new_member_roles[$mapped_to_username];
466 $rolename = $this->importer->get_user_role($user);
468 $users[$mapped_to_username] = array( 'role' => $role );
470 if ($this->message) {
471 $this->message .= '<br />';
473 $this->message .= sprintf(_('Imported user "%s" (role "%s"), mapped as "%s" which is not yet in the project : need to add it as role "%s".'),
474 $imported_username, $rolename, $mapped_to_username, $role);
477 $can_proceed = FALSE;
483 // user not found : probably messing with the form post
484 $this->feedback(sprintf(_('Failed to find mapped user "%s"'), $mapped_to_username));
485 $can_proceed = FALSE;
490 //print_r('We can proceed !');
492 if($apply) $check = FALSE;
494 // For security, for now : TODO to be removed later
496 user_fill($users, $group_id, $check);
500 $this->feedback("Couldn't proceed!");
502 $html .= "All (mapped) imported users added to the group.";
514 * @return html string
517 global $group_id, $feedback;
521 // If the posted JSON file indeed contains a project dump, an importer was created,
522 // and if it has data we can work
523 if($this->importer) {
524 // If it indeed has valid data
525 if ($this->importer->has_project_dump()) {
526 $this->message .= "Here are the results from your upload :";
528 $imported_users = $this->importer->get_users();
530 $this->importer->get_tools();
532 $projects = $this->importer->get_projects();
535 if (! $this->form_header_already_displayed) {
536 $this->form_header_already_displayed = true;
537 $html .= '<form enctype="multipart/form-data" action="'.getStringFromServer('PHP_SELF').'" method="post">';
540 // Handle missing users, taking into account the user mapping form elements
541 // that may have been provided
543 $html .= $this->match_users($imported_users, $apply);
545 // Then handle project(s)
547 if(count($projects)) {
549 // $output = '<b>Project parsed:</b><br />';
550 // $output .= '<pre>'. nl2br(htmlspecialchars(print_r($projects,True))) . '</pre>';
551 /* $output .= '<b>Trackers parsed:</b><br />';
552 foreach ($this->importer->trackers as $tracker) {
553 $output .= '<pre>'. nl2br(htmlspecialchars(print_r($tracker, True))) . '</pre>';
558 // Display project attributes
559 foreach($projects as $project) {
561 // Display project's general description
562 $html .= '<table id="project-summary-and-devs" class="my-layout-table" summary="">
565 <h2>'._('Details of imported project : ').
566 '<pre>'.$project->getUnixName().'</pre>
568 <h3>'._('Project summary').'</h3>';
569 $html .= '<p><pre>'.$project->getDescription().'</pre></p>';
571 $spaces = $project->getSpaces();
573 // if no spaces posted to be imported, display checkboxes to prompt user
574 // for spaces to be imported for next POST
575 if( ! count($this->posted_spaces_imported) ) {
577 // spaces header first
579 $html .= $this->html_generator->boxTop(_("Project's spaces found"));
580 if (! $this->form_header_already_displayed) {
581 $html .= '<form enctype="multipart/form-data" action="'.getStringFromServer('PHP_SELF').'" method="post">';
582 $this->form_header_already_displayed = true;
584 $html .= '<table width="100%"><thead><tr>';
585 $html .= '<th>'._('uri').'</th>';
586 $html .= '<th>'._('type').'</th>';
587 $html .= '<th>'._('Import space ?').'</th>';
588 $html .= '</tr></thead><tbody>';
590 foreach($spaces as $space => $spaceres) {
592 $sha_uri = sha1($uri);
593 $type = $spaceres->getPropValue('rdf:type');
596 $html .= '<td style="white-space: nowrap;">'. $uri .'</td>';
597 $html .= '<td style="white-space: nowrap;">'. $type .'</td>';
598 if(array_key_exists($sha_uri, $this->posted_spaces_imported)) {
599 $html .= '<td><input type="checkbox" name="import_'.$sha_uri.'" value="'.$sha_uri.'" selected="selected" /></td>';
602 $html .= '<td><input type="checkbox" name="import_'.$sha_uri.'" value="'.$sha_uri.'" /></td>';
607 $html .= '<input type="hidden" name="submit_spaces" value="y" />';
608 $html .= '</tbody></table>';
609 $html .= $this->html_generator->boxBottom();
612 // else, user tells us we have to import the spaces
614 // $html .= 'to be imported:';
615 // print_r($this->posted_spaces_imported);
616 // $html .= '<br />';
617 foreach($spaces as $uri => $spaceres) {
618 $sha_uri = sha1($uri);
619 // $html .= 'sha1 :'.$sha_uri.'<br />';
620 if (in_array($sha_uri, $this->posted_spaces_imported)) {
621 $html .= 'Importing :'.$uri.'<br />';
622 $this->importer->decode_space($uri, $spaceres);
628 else { // count($projects)
629 $this->feedback(_('No project found'));
632 else { // not $this->importer->has_project_dump()
633 $this->feedback(_('parsing problem'));
642 function display_main() {
643 global $feedback, $group_id;
645 // Do the work, first !
646 $html = $this->do_work();
649 echo $this->message . '<br />';
651 html_feedback_top($feedback);
655 // If invoked initially (not on callback) or if more details needed
656 // display the last part of the form for JSON file upload
657 if (! $this->form_header_already_displayed) {
658 echo '<form enctype="multipart/form-data" action="'.getStringFromServer('PHP_SELF').'" method="post">';
659 $this->form_header_already_displayed = True;
662 // If user mapping has been provided, then display it
663 if(count($this->posted_user_mapping)) {
664 foreach ($this->posted_user_mapping as $imported_user => $mapped_user) {
665 echo '<input type="hidden" name="map_'. $imported_user .'" value="'. $mapped_user .'" />';
667 echo '<input type="hidden" name="submit_mappings" value="y" />';
670 $preselected = False;
672 if ($this->posted_selecteddumpfile) {
673 $preselected = basename($this->posted_selecteddumpfile);
675 elseif ($this->posted_uploadedfile) {
676 $preselected = $this->posted_uploadedfile;
680 $selectiondialog = $this->storage->displayFileSelectionForm($preselected);
682 echo $selectiondialog;
684 if($selectiondialog) { // there are some selectable files already
686 $legend = _('Confirm selected file or upload a new one');
689 $legend = _('Select a file or upload a new one');
691 } else { // there are yet no files
692 $legend = _('Please upload a file');
695 // finally, display the file upload form
696 echo '<fieldset><legend>'. $legend .'</legend>
698 <input type="file" id="uploaded_file" name="uploaded_file" tabindex="2" size="30" />
702 echo '<input type="hidden" name="group_id" value="' . $group_id . '" />';
703 echo '<div style="text-align:center;">
704 <input type="submit" name="submit" value="Submit" />
708 site_project_footer(array());
713 // OK, we need a session
714 if (session_loggedin()) {
716 // The user should be project admin
717 if ( ! forge_check_perm('project_admin', $group_id) ) {
718 exit_permission_denied(_('You cannot import project unless you are an admin on that project'));
721 global $group_id, $feedback;
723 $this_page = new ProjectImportPage($HTML);
729 // when called back by post form we can initialize some elements provided by the user
730 if (getStringFromRequest('submit')) {
732 $this_page->initialize_from_submitted_data();
736 $message .= "You can import a project from a JSON RDF document compatible with ForgePlucker's dump format.<br />";
739 $this_page->display_headers($message);
741 $this_page->display_main();
745 exit_not_logged_in();
752 // c-file-style: "bsd"