Merge branch '2013.10' into 2014.11
[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         $maxLoginNameLength = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_USERNAME_LENGTH);
302         if (!empty($maxLoginNameLength) && strlen($loginname) > $maxLoginNameLength) {
303             $loginname = substr($loginname, 0, $maxLoginNameLength);
304         }
305         return strtolower($loginname);
306     }
307     
308     /**
309      * create default favorite for teacher
310      * 
311      * @param Tinebase_Model_FullUser $account
312      * @param Courses_Model_Course $course
313      */
314     protected function _createDefaultFilterForTeacher($account, $course)
315     {
316         $pfe = Tinebase_PersistentFilter::getInstance();
317         $filter = $pfe->create(new Tinebase_Model_PersistentFilter(array(
318             'account_id'        => $account->getId(),
319             'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Courses')->getId(),
320             'model'             => 'Courses_Model_CourseFilter',
321             'name'              => "My course", // _("My course")
322             'description'       => "My course",
323             'filters'           => array(
324                 array(
325                     'field'     => 'is_deleted',
326                     'operator'  => 'equals',
327                     'value'     => '0'
328                 ),
329                 array(
330                     'field'     => 'name',
331                     'operator'  => 'equals',
332                     'value'     => $course->name
333                 ),
334             ),
335         )));
336         
337         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Created default filter for teacher '
338             . $account->accountLoginName . ': ' . print_r($filter->toArray(), true));
339         
340         // set as default
341         $pref = new Courses_Preference();
342         $pref->setValueForUser(Courses_Preference::DEFAULTPERSISTENTFILTER, $filter->getId(), $account->getId(), TRUE);
343     }
344     
345     /**
346      * Deletes a set of records.
347      * 
348      * If one of the records could not be deleted, no record is deleted
349      * 
350      * @param   array array of record identifiers
351      * @return  void
352      * @throws Tinebase_Exception_NotFound|Tinebase_Exception
353      */
354     public function delete($_ids)
355     {
356         $courses = $this->getMultiple($_ids);
357         
358         $groupsToDelete = array();
359         $usersToDelete = array();
360         
361         foreach ($courses as $course) {
362             $groupsToDelete[] = $course->group_id;
363             $usersToDelete = array_merge($usersToDelete, $this->_groupController->getGroupMembers($course->group_id));
364         }
365         
366         Courses_Controller::getInstance()->suspendEvents();
367         
368         $this->_userController->delete(array_unique($usersToDelete));
369         $this->_groupController->delete(array_unique($groupsToDelete));
370         
371         Courses_Controller::getInstance()->resumeEvents();
372         
373         parent::delete($_ids);
374     }
375     
376     /**
377      * add existings accounts to course
378      * 
379      * @param  string  $_courseId
380      * @param  array   $_members
381      * 
382      * @todo use $user->applyOptionsAndGeneratePassword($this->_getNewUserConfig($course));
383      */
384     public function addCourseMembers($_courseId, array $_members = array())
385     {
386         $this->checkRight(Courses_Acl_Rights::ADD_EXISTING_USER);
387         
388         $course = $_courseId instanceof Courses_Model_Course ? $_courseId : $this->get($_courseId);
389         
390         $tinebaseUser  = Tinebase_User::getInstance();
391         $tinebaseGroup = Tinebase_Group::getInstance();
392         
393         $courseName = strtolower($course->name);
394         $schoolName = strtolower(Tinebase_Department::getInstance()->get($course->type)->name);
395         
396         foreach ($_members as $userId) {
397             $userId = (is_array($userId)) ? $userId['id'] : $userId;
398             $user = $tinebaseUser->getFullUserById($userId);
399             $oldPrimaryGroup = $user->accountPrimaryGroup;
400             
401             if ($this->_config->get(Courses_Config::STUDENT_LOGINNAME_PREFIX, FALSE) && ($position = strrpos($user->accountLoginName, '-')) !== false) {
402                 $user->accountLoginName = $courseName . '-' . substr($user->accountLoginName, $position + 1);
403                 
404                 //short User name
405                 $user->accountLoginName = $user->shortenUsername();
406             }
407             
408             $user->accountPrimaryGroup  = $course->group_id;
409             $tinebaseGroup->addGroupMember($user->accountPrimaryGroup, $user);
410             
411             $user->accountHomeDirectory = (isset($this->_config->basehomedir)) ? $this->_config->basehomedir . $schoolName . '/'. $courseName . '/' . $user->accountLoginName : '';
412             
413             if (isset($user->sambaSAM)) {
414                 $sambaSAM = $user->sambaSAM;
415                 
416                 $sambaSAM->homePath    = $this->_config->samba->basehomepath . $user->accountLoginName;
417                 $sambaSAM->logonScript = $courseName . $this->_config->samba->logonscript_postfix_member;
418                 $sambaSAM->profilePath = $this->_config->samba->baseprofilepath . $schoolName . '\\' . $courseName . '\\' . $user->accountLoginName;
419                 
420                 $user->sambaSAM = $sambaSAM;
421             }
422             
423             $tinebaseUser->updateUser($user);
424             $tinebaseGroup->removeGroupMember($oldPrimaryGroup, $user);
425             $this->_addToStudentGroup(array($user->getId()));
426         }
427     }
428     
429     /**
430      * add user ids to student group (if configured)
431      * 
432      * @param array $userIds
433      */
434     protected function _addToStudentGroup($userIds)
435     {
436         if (isset($this->_config->students_group) && !empty($this->_config->students_group)) {
437             
438             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
439                 . ' Adding ' . print_r($userIds, TRUE) . ' to students group (id ' . $this->_config->students_group . ')');
440             
441             foreach ($userIds as $id) {
442                 $this->_groupController->addGroupMember($this->_config->students_group, $id);
443             }
444         }
445     }
446     
447     /**
448     * import course members
449     *
450     * @param string $tempFileId
451     * @param string $courseId
452     * @return array
453     */
454     public function importMembers($tempFileId, $courseId)
455     {
456         $this->checkRight(Courses_Acl_Rights::ADD_NEW_USER);
457         
458         $tempFile = Tinebase_TempFile::getInstance()->getTempFile($tempFileId);
459     
460         // get definition and start import with admin user import csv plugin
461         $definitionName = $this->_config->get(Courses_Config::STUDENTS_IMPORT_DEFINITION, 'admin_user_import_csv');
462         
463         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Using import definition: ' . $definitionName);
464         
465         $definition = Tinebase_ImportExportDefinition::getInstance()->getByName($definitionName);
466         
467         $course = $this->get($courseId);
468         // check if group exists, too
469         $group = $this->_groupController->get($course->group_id);
470         
471         $importer = Admin_Import_User_Csv::createFromDefinition($definition, $this->_getNewUserConfig($course));
472         $result = $importer->importFile($tempFile->path);
473         
474         $groupMembers = $this->_groupController->getGroupMembers($course->group_id);
475         $this->_manageAccessGroups($groupMembers, $course);
476         $this->_addToStudentGroup($groupMembers);
477         
478         return $result;
479     }
480     
481     /**
482      * returns config for new users
483      * 
484      * @param Courses_Model_Course $course
485      * @return array
486      */
487     protected function _getNewUserConfig(Courses_Model_Course $course)
488     {
489         $schoolName = strtolower(Tinebase_Department::getInstance()->get($course->type)->name);
490         
491         return array(
492             'accountLoginNamePrefix'        => ($this->_config->get(Courses_Config::STUDENT_LOGINNAME_PREFIX, FALSE)) ? $course->name . '-' : '',
493             'group_id'                      => $course->group_id,
494             'accountEmailDomain'            => (isset($this->_config->domain)) ? $this->_config->domain : '',
495             'accountHomeDirectoryPrefix'    => (isset($this->_config->basehomedir)) ? $this->_config->basehomedir . $schoolName . '/'. $course->name . '/' : '',
496             'userNameSchema'                => $this->_config->get(Courses_Config::STUDENTS_USERNAME_SCHEMA, 1),
497             'password'                      => $this->getStudentPassword($course->name),
498             'course'                        => $course,
499             'accountLoginShell'             => '/bin/false',
500             'samba'                         => (isset($this->_config->samba)) ? array(
501                 'homePath'      => $this->_config->samba->basehomepath,
502                 'homeDrive'     => $this->_config->samba->homedrive,
503                 'logonScript'   => $course->name . $this->_config->samba->logonscript_postfix_member,
504                 'profilePath'   => $this->_config->samba->baseprofilepath . $schoolName . '\\' . $course->name . '\\',
505                 'pwdCanChange'  => new Tinebase_DateTime('@1'),
506                 'pwdMustChange' => new Tinebase_DateTime('@1')
507             ) : array(),
508         );
509     }
510     
511     
512     /**
513      * Returns default student password
514      * 
515      * @param string $courseName
516      * @return string
517      */
518     public function getStudentPassword($courseName)
519     {
520         return strtolower($courseName) . $this->_config->get(Courses_Config::STUDENT_PASSWORD_SUFFIX, '');
521     }
522     
523     /**
524      * add new member to course
525      * 
526      * @param Courses_Model_Course $course
527      * @param Tinebase_Model_FullUser $user
528      * @return Tinebase_Model_FullUser
529      * 
530      * @todo use importMembers() here to avoid duplication
531      */
532     public function createNewMember(Courses_Model_Course $course, Tinebase_Model_FullUser $user)
533     {
534         $this->checkRight(Courses_Acl_Rights::ADD_NEW_USER);
535         
536         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
537             . ' Creating new member for ' . $course->name);
538         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
539             . ' ' . print_r($course->toArray(), TRUE));
540         
541         $password = $user->applyOptionsAndGeneratePassword($this->_getNewUserConfig($course));
542         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
543             . ' ' . print_r($user->toArray(), TRUE));
544         $newMember = $this->_userController->create($user, $password, $password);
545         
546         // add to default group and manage access group for user
547         $this->_groupController->addGroupMember(Tinebase_Group::getInstance()->getDefaultGroup()->getId(), $newMember->getId());
548         $this->_manageAccessGroups(array($newMember->getId()), $course);
549         $this->_addToStudentGroup(array($newMember->getId()));
550         
551         return $newMember;
552     }
553 }