Merge branch 'pu/2013.10-groupimport'
[tine20] / tine20 / Courses / Controller / Course.php
1 <?php
2 /**
3  * Course controller for Courses application
4  * 
5  * @package     Courses
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
10  *
11  */
12
13 /**
14  * Course controller class for Courses application
15  * 
16  * @package     Courses
17  * @subpackage  Controller
18  */
19 class Courses_Controller_Course extends Tinebase_Controller_Record_Abstract
20 {
21     /**
22      * application name (is needed in checkRight())
23      *
24      * @var string
25      */
26     protected $_applicationName = 'Courses';
27     
28     /**
29      * Model name
30      *
31      * @var string
32      * 
33      * @todo perhaps we can remove that and build model name from name of the class (replace 'Controller' with 'Model')
34      */
35     protected $_modelName = 'Courses_Model_Course';
36     
37     /**
38     * the groups controller
39     *
40     * @var Admin_Controller_Group
41     */
42     protected $_groupController = NULL;
43     
44     /**
45     * the groups controller
46     *
47     * @var Admin_Controller_User
48     */
49     protected $_userController = NULL;
50     
51     /**
52      * config of courses
53      *
54      * @var Zend_Config
55      */
56     protected $_config = NULL;
57     
58     /**
59      * delete or just set is_delete=1 if record is going to be deleted
60      * - legacy code -> remove that when all backends/applications are using the history logging
61      *
62      * @var boolean
63      */
64     protected $_purgeRecords = TRUE;
65     
66     /**
67      * check for container ACLs?
68      *
69      * @var boolean
70      */
71     protected $_doContainerACLChecks = false;
72     
73     /**
74      * the constructor
75      *
76      * don't use the constructor. use the singleton 
77      */
78     private function __construct()
79     {
80         $this->_backend = new Courses_Backend_Course();
81         $this->_config = Courses_Config::getInstance();
82         $this->_groupController = Admin_Controller_Group::getInstance();
83         $this->_userController = Admin_Controller_User::getInstance();
84     }
85     
86     /**
87      * holds the instance of the singleton
88      *
89      * @var Courses_Controller_Course
90      */
91     private static $_instance = NULL;
92     
93     /**
94      * the singleton pattern
95      *
96      * @return Courses_Controller_Course
97      */
98     public static function getInstance() 
99     {
100         if (self::$_instance === NULL) {
101             self::$_instance = new Courses_Controller_Course();
102         }
103         
104         return self::$_instance;
105     }        
106
107     /****************************** overwritten functions ************************/    
108     
109     /**
110      * save course with corresponding group
111      * 
112      * @param Courses_Model_Course $course
113      * @param Tinebase_Model_Group $group
114      * @return Courses_Model_Course
115      * 
116      * @todo this should be moved to normal create/update (inspection) functions
117      */
118     public function saveCourseAndGroup(Courses_Model_Course $course, Tinebase_Model_Group $group)
119     {
120         $i18n = Tinebase_Translation::getTranslation('Courses');
121         $groupNamePrefix = $i18n->_('Course');
122         
123         $groupNamePrefix = is_array($groupNamePrefix) ? $groupNamePrefix[0] : $groupNamePrefix;
124         $group->name = $groupNamePrefix . '-' . $course->name;
125         
126         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Saving course ' . $course->name . ' with group ' . $group->name);
127         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($group->toArray(), true));
128         
129         if (empty($group->id)) {
130             $savedGroup         = $this->_groupController->create($group);
131             $course->group_id   = $savedGroup->getId();
132             $savedRecord        = $this->create($course);
133         } else {
134             $savedRecord      = $this->update($course);
135             $currentMembers   = $this->_groupController->getGroupMembers($course->group_id);
136             $newCourseMembers = array_diff((array)$group->members, $currentMembers);
137             if (count($newCourseMembers) > 0) {
138                 $this->addCourseMembers($course, $newCourseMembers);
139             }
140         
141             $deletedAccounts  = array_diff($currentMembers, (array)$group->members);
142
143             // delete members which got removed from course
144             $this->_userController->delete($deletedAccounts);
145         }
146         
147         $groupMembers = Tinebase_Group::getInstance()->getGroupMembers($course->group_id);
148         // add/remove members to/from internet/fileserver group
149         if (! empty($groupMembers)) {
150             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Found ' . count($groupMembers) . ' group members');
151             $this->_manageAccessGroups($groupMembers, $savedRecord);
152             // $this->_manageAccessGroups($group->members, $savedRecord, 'fileserver');
153         } else {
154             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No group members found.');
155         }
156         
157         return $savedRecord;
158     }
159     
160     /**
161     * add or remove members from internet/fileserver groups
162     *
163     * @param array $members array of member ids
164     * @param Courses_Model_Course $course
165     * @param string $accessType
166     * 
167     * @todo should be moved to inspectAfter*
168     * @todo allow fileserver group management, too
169     */
170     protected function _manageAccessGroups(array $members, $course, $accessType = 'internet')
171     {
172         $configField = $accessType . '_group';
173         $secondConfigField = $configField;
174         if ($course->{$accessType} === 'FILTERED') {
175             $configField .= '_filtered';
176         } else {
177             $secondConfigField .= '_filtered';
178         }
179     
180         if (! isset($this->_config->{$configField})) {
181             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No config found for ' . $configField);
182             return;
183         }
184     
185         $groupId = $this->_config->{$configField};
186         $secondGroupId = ($accessType === 'internet' && isset($this->_config->{$secondConfigField})) ? $this->_config->{$secondConfigField} : NULL;
187     
188         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
189             . " Setting $accessType to " . $course->{$accessType} . " for " . print_r($members, true));
190     
191         // add or remove members to or from internet/fileserver groups
192         foreach ($members as $memberId) {
193             try {
194                 if ($course->{$accessType} === 'ON' || $course->{$accessType} === 'FILTERED') {
195                     $this->_groupController->addGroupMember($groupId, $memberId);
196                     if ($secondGroupId) {
197                         $this->_groupController->removeGroupMember($secondGroupId, $memberId);
198                     }
199                 } else if ($course->{$accessType} === 'OFF') {
200                     $this->_groupController->removeGroupMember($groupId, $memberId);
201                     if ($secondGroupId) {
202                         $this->_groupController->removeGroupMember($secondGroupId, $memberId);
203                     }
204                 }
205             } catch (Tinebase_Exception_NotFound $tenf) {
206                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
207                     . ' ' . $tenf);
208                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
209                     . ' Removing member from course / group ' . $course->name . ' / group id: ' . $course->group_id);
210                 $this->_groupController->removeGroupMemberFromSqlBackend($course->group_id, $memberId);
211             }
212         }
213     }
214     
215     /**
216     * inspect creation of one record (after create)
217     *
218     * @param   Tinebase_Record_Interface $_createdRecord
219     * @param   Tinebase_Record_Interface $_record
220     * @return  void
221     */
222     protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
223     {
224         $teacherAccount = $this->_addNewTeacherAccount($_createdRecord);
225         
226         // add to teacher group if available
227         if (isset($this->_config->teacher_group) && !empty($this->_config->teacher_group)) {
228             $this->_groupController->addGroupMember($this->_config->teacher_group, $teacherAccount->getId());
229         }
230         
231         // add to students group if available
232         if (isset($this->_config->students_group) && !empty($this->_config->students_group)) {
233             $this->_groupController->addGroupMember($this->_config->students_group, $teacherAccount->getId());
234         }
235     }
236     
237     /**
238      * add new teacher account to course
239      * 
240      * @param Courses_Model_Course $course
241      * @return Tinebase_Model_FullUser
242      */
243     protected function _addNewTeacherAccount($course)
244     {
245         $i18n = Tinebase_Translation::getTranslation('Courses');
246         
247         $courseName = strtolower($course->name);
248         $loginName  = $this->_getTeacherLoginName($course, $i18n);
249         $schoolName = strtolower(Tinebase_Department::getInstance()->get($course->type)->name);
250         
251         $account = new Tinebase_Model_FullUser(array(
252             'accountLoginName'      => $loginName,
253             'accountLoginShell'     => '/bin/false',
254             'accountStatus'         => 'enabled',
255             'accountPrimaryGroup'   => $course->group_id,
256             'accountLastName'       => $i18n->_('Teacher'),
257             'accountDisplayName'    => $course->name . ' ' .  $i18n->_('Teacher Account'),
258             'accountFirstName'      => $course->name,
259             'accountExpires'        => NULL,
260             'accountEmailAddress'   => (isset($this->_config->domain) && !empty($this->_config->domain)) ? $loginName . '@' . $this->_config->domain : '',
261             'accountHomeDirectory'  => (isset($this->_config->basehomedir)) ? $this->_config->basehomedir . $schoolName . '/'. $courseName . '/' . $loginName : '',
262         ));
263         
264         if (isset($this->_config->samba)) {
265             $samUser = new Tinebase_Model_SAMUser(array(
266                 'homePath'    => $this->_config->samba->basehomepath . $loginName,
267                 'homeDrive'   => $this->_config->samba->homedrive,
268                 'logonScript' => $courseName . $this->_config->samba->logonscript_postfix_teacher,
269                 'profilePath' => $this->_config->samba->baseprofilepath . $schoolName . '\\' . $courseName . '\\' . $loginName
270             ));
271         
272             $account->sambaSAM = $samUser;
273         }
274         
275         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Created teacher account for course '
276             . $course->name . ': ' . print_r($account->toArray(), true));
277         
278         $password = $this->_config->get('teacher_password', $account->accountLoginName);
279         $account = $this->_userController->create($account, $password, $password);
280         $this->_groupController->addGroupMember(Tinebase_Group::getInstance()->getDefaultGroup()->getId(), $account->getId());
281         
282         $this->_createDefaultFilterForTeacher($account, $course);
283         
284         return $account;
285     }
286     
287     /**
288      * create new teacher login name from course name
289      * 
290      * @param Courses_Model_Course $course
291      * @param Zend_Translate $i18n
292      * @return string
293      */
294     protected function _getTeacherLoginName($course, $i18n = NULL)
295     {
296         if ($i18n === NULL) {
297             $i18n = Tinebase_Translation::getTranslation('Courses');
298         }
299         
300         $loginname = $i18n->_('Teacher') . Courses_Config::getInstance()->get('teacher_login_name_delimiter', '-') . $course->name;
301         return strtolower($loginname);
302     }
303     
304     /**
305      * create default favorite for teacher
306      * 
307      * @param Tinebase_Model_FullUser $account
308      * @param Courses_Model_Course $course
309      */
310     protected function _createDefaultFilterForTeacher($account, $course)
311     {
312         $pfe = Tinebase_PersistentFilter::getInstance();
313         $filter = $pfe->create(new Tinebase_Model_PersistentFilter(array(
314             'account_id'        => $account->getId(),
315             'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Courses')->getId(),
316             'model'             => 'Courses_Model_CourseFilter',
317             'name'              => "My course", // _("My course")
318             'description'       => "My course",
319             'filters'           => array(
320                 array(
321                     'field'     => 'is_deleted',
322                     'operator'  => 'equals',
323                     'value'     => '0'
324                 ),
325                 array(
326                     'field'     => 'name',
327                     'operator'  => 'equals',
328                     'value'     => $course->name
329                 ),
330             ),
331         )));
332         
333         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Created default filter for teacher '
334             . $account->accountLoginName . ': ' . print_r($filter->toArray(), true));
335         
336         // set as default
337         $pref = new Courses_Preference();
338         $pref->setValueForUser(Courses_Preference::DEFAULTPERSISTENTFILTER, $filter->getId(), $account->getId(), TRUE);
339     }
340     
341     /**
342      * Deletes a set of records.
343      * 
344      * If one of the records could not be deleted, no record is deleted
345      * 
346      * @param   array array of record identifiers
347      * @return  void
348      * @throws Tinebase_Exception_NotFound|Tinebase_Exception
349      */
350     public function delete($_ids)
351     {
352         $courses = $this->getMultiple($_ids);
353         
354         $groupsToDelete = array();
355         $usersToDelete = array();
356         
357         foreach ($courses as $course) {
358             $groupsToDelete[] = $course->group_id;
359             $usersToDelete = array_merge($usersToDelete, $this->_groupController->getGroupMembers($course->group_id));
360         }
361         
362         Courses_Controller::getInstance()->suspendEvents();
363         
364         $this->_userController->delete(array_unique($usersToDelete));
365         $this->_groupController->delete(array_unique($groupsToDelete));
366         
367         Courses_Controller::getInstance()->resumeEvents();
368         
369         parent::delete($_ids);
370     }
371     
372     /**
373      * add existings accounts to course
374      * 
375      * @param  string  $_courseId
376      * @param  array   $_members
377      * 
378      * @todo use $user->applyOptionsAndGeneratePassword($this->_getNewUserConfig($course));
379      */
380     public function addCourseMembers($_courseId, array $_members = array())
381     {
382         $this->checkRight(Courses_Acl_Rights::ADD_EXISTING_USER);
383         
384         $course = $_courseId instanceof Courses_Model_Course ? $_courseId : $this->get($_courseId);
385         
386         $tinebaseUser  = Tinebase_User::getInstance();
387         $tinebaseGroup = Tinebase_Group::getInstance();
388         
389         $courseName = strtolower($course->name);
390         $schoolName = strtolower(Tinebase_Department::getInstance()->get($course->type)->name);
391         
392         foreach ($_members as $userId) {
393             $userId = (is_array($userId)) ? $userId['id'] : $userId;
394             $user = $tinebaseUser->getFullUserById($userId);
395             
396             $tinebaseGroup->removeGroupMember($user->accountPrimaryGroup, $user);
397             
398             if ($this->_config->get(Courses_Config::STUDENT_LOGINNAME_PREFIX, FALSE) && ($position = strrpos($user->accountLoginName, '-')) !== false) {
399                 $user->accountLoginName = $courseName . '-' . substr($user->accountLoginName, $position + 1);
400             }
401             
402             $user->accountPrimaryGroup  = $course->group_id;
403             $user->accountHomeDirectory = (isset($this->_config->basehomedir)) ? $this->_config->basehomedir . $schoolName . '/'. $courseName . '/' . $user->accountLoginName : '';
404             
405             if (isset($user->sambaSAM)) {
406                 $sambaSAM = $user->sambaSAM;
407                 
408                 $sambaSAM->homePath    = $this->_config->samba->basehomepath . $user->accountLoginName;
409                 $sambaSAM->logonScript = $courseName . $this->_config->samba->logonscript_postfix_member;
410                 $sambaSAM->profilePath = $this->_config->samba->baseprofilepath . $schoolName . '\\' . $courseName . '\\' . $user->accountLoginName;
411                 
412                 $user->sambaSAM = $sambaSAM;
413             }
414             
415             $tinebaseUser->updateUser($user);
416             $tinebaseGroup->addGroupMember($user->accountPrimaryGroup, $user);
417             $this->_addToStudentGroup(array($user->getId()));
418         }
419     }
420     
421     /**
422      * add user ids to student group (if configured)
423      * 
424      * @param array $userIds
425      */
426     protected function _addToStudentGroup($userIds)
427     {
428         if (isset($this->_config->students_group) && !empty($this->_config->students_group)) {
429             
430             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
431                 . ' Adding ' . print_r($userIds, TRUE) . ' to students group (id ' . $this->_config->students_group . ')');
432             
433             foreach ($userIds as $id) {
434                 $this->_groupController->addGroupMember($this->_config->students_group, $id);
435             }
436         }
437     }
438     
439     /**
440     * import course members
441     *
442     * @param string $tempFileId
443     * @param string $courseId
444     * @return array
445     */
446     public function importMembers($tempFileId, $courseId)
447     {
448         $this->checkRight(Courses_Acl_Rights::ADD_NEW_USER);
449         
450         $tempFile = Tinebase_TempFile::getInstance()->getTempFile($tempFileId);
451     
452         // get definition and start import with admin user import csv plugin
453         $definitionName = $this->_config->get(Courses_Config::STUDENTS_IMPORT_DEFINITION, 'admin_user_import_csv');
454         
455         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Using import definition: ' . $definitionName);
456         
457         $definition = Tinebase_ImportExportDefinition::getInstance()->getByName($definitionName);
458         
459         $course = $this->get($courseId);
460         // check if group exists, too
461         $group = $this->_groupController->get($course->group_id);
462         
463         $importer = Admin_Import_User_Csv::createFromDefinition($definition, $this->_getNewUserConfig($course));
464         $result = $importer->importFile($tempFile->path);
465         
466         $groupMembers = $this->_groupController->getGroupMembers($course->group_id);
467         $this->_manageAccessGroups($groupMembers, $course);
468         $this->_addToStudentGroup($groupMembers);
469         
470         return $result;
471     }
472     
473     /**
474      * returns config for new users
475      * 
476      * @param Courses_Model_Course $course
477      * @return array
478      */
479     protected function _getNewUserConfig(Courses_Model_Course $course)
480     {
481         $schoolName = strtolower(Tinebase_Department::getInstance()->get($course->type)->name);
482         
483         return array(
484             'accountLoginNamePrefix'        => ($this->_config->get(Courses_Config::STUDENT_LOGINNAME_PREFIX, FALSE)) ? $course->name . '-' : '',
485             'group_id'                      => $course->group_id,
486             'accountEmailDomain'            => (isset($this->_config->domain)) ? $this->_config->domain : '',
487             'accountHomeDirectoryPrefix'    => (isset($this->_config->basehomedir)) ? $this->_config->basehomedir . $schoolName . '/'. $course->name . '/' : '',
488             'userNameSchema'                => $this->_config->get(Courses_Config::STUDENTS_USERNAME_SCHEMA, 1),
489             'password'                      => strtolower($course->name),
490             'course'                        => $course,
491             'accountLoginShell'             => '/bin/false',
492             'samba'                         => (isset($this->_config->samba)) ? array(
493                 'homePath'      => $this->_config->samba->basehomepath,
494                 'homeDrive'     => $this->_config->samba->homedrive,
495                 'logonScript'   => $course->name . $this->_config->samba->logonscript_postfix_member,
496                 'profilePath'   => $this->_config->samba->baseprofilepath . $schoolName . '\\' . $course->name . '\\',
497                 'pwdCanChange'  => new Tinebase_DateTime('@1'),
498                 'pwdMustChange' => new Tinebase_DateTime('@1')
499             ) : array(),
500         );
501     }
502     
503     /**
504      * add new member to course
505      * 
506      * @param Courses_Model_Course $course
507      * @param Tinebase_Model_FullUser $user
508      * @return Tinebase_Model_FullUser
509      * 
510      * @todo use importMembers() here to avoid duplication
511      */
512     public function createNewMember(Courses_Model_Course $course, Tinebase_Model_FullUser $user)
513     {
514         $this->checkRight(Courses_Acl_Rights::ADD_NEW_USER);
515         
516         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
517             . ' Creating new member for ' . $course->name);
518         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
519             . ' ' . print_r($course->toArray(), TRUE));
520         
521         $password = $user->applyOptionsAndGeneratePassword($this->_getNewUserConfig($course));
522         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
523             . ' ' . print_r($user->toArray(), TRUE));
524         $newMember = $this->_userController->create($user, $password, $password);
525         
526         // add to default group and manage access group for user
527         $this->_groupController->addGroupMember(Tinebase_Group::getInstance()->getDefaultGroup()->getId(), $newMember->getId());
528         $this->_manageAccessGroups(array($newMember->getId()), $course);
529         $this->_addToStudentGroup(array($newMember->getId()));
530         
531         return $newMember;
532     }
533 }