瀏覽代碼

Merge branch '1.11.x' of github.com:chamilo/chamilo-lms into 1.11.x

Yannick Warnier 8 年之前
父節點
當前提交
3911003fde
共有 95 個文件被更改,包括 2026 次插入216 次删除
  1. 3 0
      .htaccess
  2. 3 3
      main/admin/access_url_edit.php
  3. 15 13
      main/attendance/attendance_controller.php
  4. 1 1
      main/blog/blog_admin.php
  5. 5 1
      main/calendar/agenda_js.php
  6. 1 1
      main/document/create_audio.php
  7. 1 1
      main/document/create_document.php
  8. 1 1
      main/document/create_draw.php
  9. 1 1
      main/document/create_paint.php
  10. 1 1
      main/document/record_audio.php
  11. 1 1
      main/document/webcam_clip.php
  12. 1 1
      main/dropbox/dropbox_download.php
  13. 5 1
      main/dropbox/dropbox_functions.inc.php
  14. 2 2
      main/dropbox/dropbox_init.inc.php
  15. 1 1
      main/exercise/exercise.php
  16. 1 1
      main/exercise/exercise_result.php
  17. 1 1
      main/exercise/exercise_submit_modal.php
  18. 1 0
      main/forum/index.php
  19. 1 1
      main/forum/viewthread_flat.inc.php
  20. 1 1
      main/forum/viewthread_nested.inc.php
  21. 1 1
      main/forum/viewthread_threaded.inc.php
  22. 1 1
      main/gradebook/lib/GradebookUtils.php
  23. 1 1
      main/group/group_overview.php
  24. 5 1
      main/inc/ajax/agenda.ajax.php
  25. 1 1
      main/inc/global.inc.php
  26. 2 3
      main/inc/lib/agenda.lib.php
  27. 4 0
      main/inc/lib/api.lib.php
  28. 1 1
      main/inc/lib/course_home.lib.php
  29. 64 53
      main/inc/lib/fileUpload.lib.php
  30. 1 0
      main/inc/lib/formvalidator/FormValidator.class.php
  31. 10 30
      main/inc/lib/pear/HTML/QuickForm/file.php
  32. 6 0
      main/inc/lib/plugin.class.php
  33. 22 0
      main/inc/lib/sessionmanager.lib.php
  34. 1 1
      main/inc/lib/tracking.lib.php
  35. 28 2
      main/inc/local.inc.php
  36. 1 1
      main/mySpace/myStudents.php
  37. 1 1
      main/session/index.php
  38. 1 1
      main/tracking/course_log_tools.php
  39. 1 1
      main/user/user.php
  40. 63 0
      plugin/buycourses/database.php
  41. 15 1
      plugin/buycourses/lang/spanish.php
  42. 30 1
      plugin/buycourses/resources/css/style.css
  43. 426 3
      plugin/buycourses/src/buy_course_plugin.class.php
  44. 13 7
      plugin/buycourses/src/configuration.php
  45. 2 0
      plugin/buycourses/src/course_catalog.php
  46. 1 0
      plugin/buycourses/src/culqi.php
  47. 2 0
      plugin/buycourses/src/sales_report.php
  48. 89 0
      plugin/buycourses/src/service_catalog.php
  49. 35 0
      plugin/buycourses/src/service_error.php
  50. 39 0
      plugin/buycourses/src/service_information.php
  51. 234 0
      plugin/buycourses/src/service_process.php
  52. 95 0
      plugin/buycourses/src/service_sales_report.php
  53. 123 0
      plugin/buycourses/src/services_add.php
  54. 140 0
      plugin/buycourses/src/services_edit.php
  55. 2 0
      plugin/buycourses/src/session_catalog.php
  56. 3 0
      plugin/buycourses/uploads/services/images/.gitignore
  57. 52 0
      plugin/buycourses/view/catalog.tpl
  58. 66 2
      plugin/buycourses/view/configuration.tpl
  59. 91 3
      plugin/buycourses/view/process.tpl
  60. 13 0
      plugin/buycourses/view/sales_report.tpl
  61. 80 0
      plugin/buycourses/view/service_information.tpl
  62. 98 0
      plugin/buycourses/view/service_sales_report.tpl
  63. 1 0
      plugin/courseblock/CourseBlockPlugin.php
  64. 32 11
      plugin/google_maps/view/map_coordinates.tpl
  65. 1 1
      plugin/vchamilo/lib/Virtual.php
  66. 1 1
      plugin/vchamilo/views/manage.php
  67. 25 6
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/AgendaStudent.php
  68. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Announcements.php
  69. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/AssessmentsIntroduction.php
  70. 10 3
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Basic.php
  71. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Careers.php
  72. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Documents.php
  73. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/DocumentsStudent.php
  74. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/FAQ.php
  75. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Forum.php
  76. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ForumStudent.php
  77. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Glossary.php
  78. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/LearningPathDocuments.php
  79. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Messages.php
  80. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/NotebookStudent.php
  81. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/PortalNews.php
  82. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Profile.php
  83. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Project.php
  84. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ProjectComment.php
  85. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ProjectCommentStudent.php
  86. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ProjectStudent.php
  87. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Survey.php
  88. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestAnswerFeedback.php
  89. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestFreeAnswer.php
  90. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestProposedAnswer.php
  91. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestQuestionDescription.php
  92. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Wiki.php
  93. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/WikiStudent.php
  94. 2 2
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/WikiTask.php
  95. 1 1
      src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Work.php

+ 3 - 0
.htaccess

@@ -53,5 +53,8 @@ RewriteRule ^main/exercice/(.*)$ main/exercise/$1 [QSA,L]
 # Support old URLs using the newscorm folder rather than lp
 RewriteRule ^main/newscorm/(.*)$ main/lp/$1 [QSA,L]
 
+# service Information
+RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
+
 # Deny access
 RewriteRule ^(tests|.git) - [F,L,NC]

+ 3 - 3
main/admin/access_url_edit.php

@@ -24,10 +24,10 @@ if ($form->validate()) {
         $url_array = $form->getSubmitValues();
         $url = Security::remove_XSS($url_array['url']);
         $description = Security::remove_XSS($url_array['description']);
-        $active = intval($url_array['active']);
-        $url_id = $url_array['id'];
+        $active = isset($url_array['active']) ? intval($url_array['active']) : 0;
+        $url_id = isset($url_array['id']) ? intval($url_array['id']) : 0;
         $url_to_go = 'access_urls.php';
-        if ($url_id != '') {
+        if (!empty($url_id)) {
             //we can't change the status of the url with id=1
             if ($url_id == 1) {
                 $active = 1;

+ 15 - 13
main/attendance/attendance_controller.php

@@ -25,12 +25,10 @@ class AttendanceController
     }
 
     /**
-     * It's used for listing attendace,
+     * It's used for listing attendance,
      * render to attendance_list view
-     * @param boolean   true for listing history (optional)
-     * @param array 	message for showing by action['edit','add','delete'] (optional)
      */
-    public function attendance_list($history = false, $messages = array())
+    public function attendance_list()
     {
         $data = array();
 
@@ -53,20 +51,23 @@ class AttendanceController
         if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') {
             if (!empty($_POST['title'])) {
                 $check = Security::check_token();
+                $last_id = 0;
                 if ($check) {
                     $attendance->set_name($_POST['title']);
                     $attendance->set_description($_POST['description']);
                     $attendance->set_attendance_qualify_title($_POST['attendance_qualify_title']);
                     $attendance->set_attendance_weight($_POST['attendance_weight']);
                     $link_to_gradebook = false;
-                    if ( isset($_POST['attendance_qualify_gradebook']) && $_POST['attendance_qualify_gradebook'] == 1 ) {
+                    if (isset($_POST['attendance_qualify_gradebook']) &&
+                        $_POST['attendance_qualify_gradebook'] == 1
+                    ) {
                         $link_to_gradebook = true;
                     }
-                    $attendance->category_id = $_POST['category_id'];
+                    $attendance->category_id = isset($_POST['category_id']) ? $_POST['category_id'] : 0;
                     $last_id = $attendance->attendance_add($link_to_gradebook);
                     Security::clear_token();
                 }
-                header('location:index.php?action=calendar_add&attendance_id='.$last_id.'&'.api_get_cidreq());
+                header('Location: index.php?action=calendar_add&attendance_id='.$last_id.'&'.api_get_cidreq());
                 exit;
             } else {
                 $data['error'] = true;
@@ -84,9 +85,9 @@ class AttendanceController
     }
 
     /**
-     * It's used for editing attendace,
+     * It's used for editing attendance,
      * render to attendance_edit or attendance_list view
-     * @param int	attendance id
+     * @param int	$attendance_id
      */
     public function attendance_edit($attendance_id)
     {
@@ -153,7 +154,7 @@ class AttendanceController
     /**
      * It's used for delete attendaces
      * render to attendance_list view
-     * @param int	attendance id
+     * @param int	$attendance_id
      */
     public function attendance_delete($attendance_id)
     {
@@ -178,7 +179,7 @@ class AttendanceController
     /**
      * It's used for make attendance visible
      * render to attendance_list view
-     * @param int	$attendanceId
+     * @param int $attendanceId
      */
     public function attendanceSetVisible($attendanceId)
     {
@@ -196,7 +197,7 @@ class AttendanceController
     /**
      * It's used for make attendance invisible
      * render to attendance_list view
-     * @param int	$attendanceId
+     * @param int $attendanceId
      */
     public function attendanceSetInvisible($attendanceId)
     {
@@ -212,11 +213,12 @@ class AttendanceController
 
     /**
      * Restores an attendance entry and fallback to attendances rendering
-     * @param int	$attendance_id
+     * @param int $attendance_id
      */
     public function attendance_restore($attendance_id)
     {
         $attendance = new Attendance();
+        $affected_rows = false;
         if (!empty($attendance_id)) {
             $affected_rows = $attendance->attendance_restore($attendance_id);
         }

+ 1 - 1
main/blog/blog_admin.php

@@ -19,7 +19,7 @@ $blog_table_attachment = Database::get_course_table(TABLE_BLOGS_ATTACHMENT);
 api_protect_course_script(true);
 
 //	 ONLY USERS REGISTERED IN THE COURSE
-if ((!$is_allowed_in_course || !$is_courseMember) && !api_is_allowed_to_edit()) {
+if ((!api_is_allowed_in_course() || !$is_courseMember) && !api_is_allowed_to_edit()) {
     api_not_allowed(true);//print headers/footers
 }
 

+ 5 - 1
main/calendar/agenda_js.php

@@ -51,6 +51,7 @@ $agenda->type = $type;
 $is_group_tutor = false;
 $session_id = api_get_session_id();
 $group_id = api_get_group_id();
+$courseId = api_get_course_int_id();
 
 if (!empty($group_id)) {
     $group_properties = GroupManager::get_group_properties($group_id);
@@ -193,12 +194,15 @@ if (!empty($userId)) {
     $agenda_ajax_url = api_get_path(WEB_AJAX_PATH).'agenda.ajax.php?type='.$type;
 }
 
+if ($type == 'course' && !empty($courseId)) {
+    $agenda_ajax_url .= '&'.api_get_cidreq();
+}
+
 if (isset($_GET['session_id'])) {
     $agenda_ajax_url .= '&session_id='.intval($_GET['session_id']);
 }
 
 $tpl->assign('web_agenda_ajax_url', $agenda_ajax_url);
-$course_code = api_get_course_id();
 
 $form = new FormValidator(
     'form',

+ 1 - 1
main/document/create_audio.php

@@ -98,7 +98,7 @@ $interbreadcrumb[] = array(
     "name" => get_lang('Documents')
 );
 
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
     api_not_allowed(true);
 }
 

+ 1 - 1
main/document/create_document.php

@@ -217,7 +217,7 @@ if (!$is_certificate_mode) {
 	$interbreadcrumb[]= array('url' => '../gradebook/'.$_SESSION['gradebook_dest'], 'name' => get_lang('Gradebook'));
 }
 
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
 	api_not_allowed(true);
 }
 

+ 1 - 1
main/document/create_draw.php

@@ -90,7 +90,7 @@ $interbreadcrumb[] = array(
 	"name" => get_lang('Documents')
 );
 
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
 	api_not_allowed(true);
 }
 

+ 1 - 1
main/document/create_paint.php

@@ -94,7 +94,7 @@ $interbreadcrumb[] = array(
     "name" => get_lang('Documents'),
 );
 
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
     api_not_allowed(true);
 }
 

+ 1 - 1
main/document/record_audio.php

@@ -80,7 +80,7 @@ if (!empty($groupId)) {
 
 $interbreadcrumb[] = array("url" => "./document.php?id=".$document_id.'&'.api_get_cidreq(), "name" => get_lang('Documents'));
 
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
 	api_not_allowed(true);
 }
 

+ 1 - 1
main/document/webcam_clip.php

@@ -82,7 +82,7 @@ if (!empty($groupId)) {
 
 $interbreadcrumb[] = array ("url" => "./document.php?id=".$document_id."&".api_get_cidreq(), "name" => get_lang('Documents'));
 
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
 	api_not_allowed(true);
 }
 

+ 1 - 1
main/dropbox/dropbox_download.php

@@ -55,7 +55,7 @@ if (isset($_GET['cat_id']) &&
         $files_to_download[] = $row['id'];
     }
     if (!is_array($files_to_download) || empty($files_to_download)) {
-        header('location: index.php?view='.Security::remove_XSS($_GET['sent_received']).'&error=ErrorNoFilesInFolder');
+        header('Location: index.php?'.api_get_cidreq().'&view='.Security::remove_XSS($_GET['sent_received']).'&error=ErrorNoFilesInFolder');
         exit;
     }
     zip_download($files_to_download);

+ 5 - 1
main/dropbox/dropbox_functions.inc.php

@@ -446,7 +446,11 @@ function display_addcategory_form($category_name = '', $id = '', $action)
         $text = get_lang('CreateCategory');
     }
 
-    $form = new FormValidator('add_new_category', 'post', api_get_self().'?view='.Security::remove_XSS($_GET['view']));
+    $form = new FormValidator(
+        'add_new_category',
+        'post',
+        api_get_self().'?'.api_get_cidreq().'&view='.Security::remove_XSS($_GET['view'])
+    );
     $form->addElement('header', $title);
 
     if (isset($id) && $id != '') {

+ 2 - 2
main/dropbox/dropbox_init.inc.php

@@ -292,14 +292,14 @@ if (!$view || $view == 'received') {
 } elseif ($view = 'sent') {
     $part = 'sent';
 } else {
-    header('location: index.php?view='.$view.'&error=Error');
+    header('location: index.php?'.api_get_cidreq().'&view='.$view.'&error=Error');
     exit;
 }
 
 if (($postAction == 'download_received' || $postAction == 'download_sent') and !$_POST['store_feedback']) {
     $checked_file_ids = $_POST['id'];
     if (!is_array($checked_file_ids) || count($checked_file_ids) == 0) {
-        header('location: index.php?view='.$view.'&error=CheckAtLeastOneFile');
+        header('Location: index.php?'.api_get_cidreq().'&view='.$view.'&error=CheckAtLeastOneFile');
     } else {
         handle_multiple_actions();
     }

+ 1 - 1
main/exercise/exercise.php

@@ -711,7 +711,7 @@ if (!empty($exerciseList)) {
                 }
                 //$class_tip = 'exercise_link';
                 $url = $move.'<a '.$alt_title.' class="'.$class_tip.'" id="tooltip_'.$row['id'].'" href="overview.php?'.api_get_cidreq().$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'">
-                             '.Display::return_icon('quiz.gif', $row['title']).'
+                             '.Display::return_icon('quiz.png', $row['title']).'
                  '.$title.' </a>';
 
                 $item = Display::tag('td', $url.' '.$session_img.$lp_blocked);

+ 1 - 1
main/exercise/exercise_result.php

@@ -207,7 +207,7 @@ if ($origin != 'learnpath') {
 	Display::display_footer();
 } else {
 	$lp_mode = isset($_SESSION['lp_mode']) ? $_SESSION['lp_mode'] : null;
-	$url = '../lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$learnpath_id.'&lp_item_id='.$learnpath_item_id.'&exeId='.$exercise_stat_info['exe_id'].'&fb_type='.$objExercise->feedback_type;
+	$url = '../lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$learnpath_id.'&lp_item_id='.$learnpath_item_id.'&exeId='.$exercise_stat_info['exe_id'].'&fb_type='.$objExercise->feedback_type.'#atoc_'.$learnpath_item_id;
 	$href = ($lp_mode == 'fullscreen')?' window.opener.location.href="'.$url.'" ':' top.location.href="'.$url.'"';
 
     if (api_is_allowed_to_session_edit()) {

+ 1 - 1
main/exercise/exercise_submit_modal.php

@@ -532,7 +532,7 @@ if ($destinationid==-1) {
 		$objQuestionTmp = Question :: read($destinationid);
 		$questionName=$objQuestionTmp->selectTitle();
 		$num_value_array= (array_keys($questionList, $destinationid));
-		$links.= Display :: return_icon('quiz.gif', '', array ('style' => 'padding-left:0px;padding-right:5px;')).'<a onclick="SendEx('.$num_value_array[0].');" href="#">'.get_lang('GoToQuestion').' '.$num_value_array[0].'</a><br /><br />';
+		$links.= Display :: return_icon('quiz.png', '', array ('style' => 'padding-left:0px;padding-right:5px;')).'<a onclick="SendEx('.$num_value_array[0].');" href="#">'.get_lang('GoToQuestion').' '.$num_value_array[0].'</a><br /><br />';
 	}
 }
 

+ 1 - 0
main/forum/index.php

@@ -346,6 +346,7 @@ if (is_array($forumCategories)) {
         if (!empty($forumsInCategory)) {
             // We display all the forums in this category.
             foreach ($forum_list as $forum) {
+                $html = '';
                 // Here we clean the whatnew_post_info array a little bit because to display the icon we
                 // test if $whatsnew_post_info[$forum['forum_id']] is empty or not.
                 if (isset($forum['forum_id'])) {

+ 1 - 1
main/forum/viewthread_flat.inc.php

@@ -258,7 +258,7 @@ if (isset($current_thread['thread_id'])) {
                         . "&thread=" . $clean_thread_id . "&action=list&post=" . $row['post_id']
                         . "&user=" . $row['poster_id'] . "&user_id=" . $row['poster_id']
                         . "&origin=" . $origin . "&idtextqualify=" . $current_qualify_thread . "\" >"
-                        . Display::return_icon('quiz.gif', get_lang('Qualify'))
+                        . Display::return_icon('quiz.png', get_lang('Qualify'))
                         . "</a> ";
                 }
             }

+ 1 - 1
main/forum/viewthread_nested.inc.php

@@ -197,7 +197,7 @@ foreach ($rows as $post) {
                     . "&forum=$clean_forum_id&thread=$clean_thread_id&action=list&post={$post['post_id']}"
                     . "&user={$post['user_id']}&user_id={$post['user_id']}"
                     . "&idtextqualify=$current_qualify_thread"
-                    . "\" >" . Display::return_icon('quiz.gif', get_lang('Qualify')) . "</a>";
+                    . "\" >" . Display::return_icon('quiz.png', get_lang('Qualify')) . "</a>";
             }
         }
     }

+ 1 - 1
main/forum/viewthread_threaded.inc.php

@@ -350,7 +350,7 @@ if ($userCanQualify) {
                 $rows[$display_post_id]['user_id'] . "&origin=" . $origin .
                 "&idtextqualify=" . $current_qualify_thread .
                 "\" >" . Display::return_icon(
-                    'quiz.gif',
+                    'quiz.png',
                     get_lang('Qualify')
                 ) . "</a>";
         }

+ 1 - 1
main/gradebook/lib/GradebookUtils.php

@@ -142,7 +142,7 @@ class GradebookUtils
                 break;
             case 'exercise':
             case LINK_EXERCISE:
-                $icon = 'quiz.gif';
+                $icon = 'quiz.png';
                 break;
             case 'learnpath':
             case LINK_LEARNPATH:

+ 1 - 1
main/group/group_overview.php

@@ -67,7 +67,7 @@ if (isset($_GET['action'])) {
 $interbreadcrumb[] = array('url' => 'group.php?'.api_get_cidreq(), 'name' => get_lang('Groups'));
 if (!isset ($_GET['origin']) || $_GET['origin'] != 'learnpath') {
     // So we are not in learnpath tool
-    if (!$is_allowed_in_course) {
+    if (!api_is_allowed_in_course()) {
         api_not_allowed(true);
     }
     if (!api_is_allowed_to_edit(false, true)) {

+ 5 - 1
main/inc/ajax/agenda.ajax.php

@@ -20,7 +20,6 @@ if ($type == 'course') {
 }
 
 $group_id = api_get_group_id();
-
 $is_group_tutor = GroupManager::is_tutor_of_group(api_get_user_id(), $group_id);
 
 $agenda = new Agenda();
@@ -34,6 +33,11 @@ switch ($action) {
         $add_as_announcement = isset($_REQUEST['add_as_annonuncement']) ? $_REQUEST['add_as_annonuncement'] : null;
         $comment = isset($_REQUEST['comment']) ? $_REQUEST['comment'] : null;
         $userToSend = isset($_REQUEST['users_to_send']) ? $_REQUEST['users_to_send'] : array();
+
+        if ($type === 'course') {
+            $agenda->set_course(api_get_course_info());
+        }
+
         echo $agenda->addEvent(
             $_REQUEST['start'],
             $_REQUEST['end'],

+ 1 - 1
main/inc/global.inc.php

@@ -273,7 +273,7 @@ $result = & api_get_settings('Plugins', 'list', $_configuration['access_url']);
 $_plugins = array();
 foreach ($result as & $row) {
     $key = & $row['variable'];
-    if (is_string($_setting[$key])) {
+    if (isset($_setting[$key]) && is_string($_setting[$key])) {
         $_setting[$key] = array();
     }
     $_setting[$key][] = $row['selected_value'];

+ 2 - 3
main/inc/lib/agenda.lib.php

@@ -27,14 +27,13 @@ class Agenda
      */
     public function __construct($senderId = 0, $courseId = 0, $sessionId = 0)
     {
-        //Table definitions
+        // Table definitions
         $this->tbl_global_agenda = Database::get_main_table(TABLE_MAIN_SYSTEM_CALENDAR);
         $this->tbl_personal_agenda = Database::get_main_table(TABLE_PERSONAL_AGENDA);
         $this->tbl_course_agenda = Database::get_course_table(TABLE_AGENDA);
         $this->table_repeat = Database::get_course_table(TABLE_AGENDA_REPEAT);
 
-        //Setting the course object if we are in a course
-        unset($this->course);
+        // Setting the course object if we are in a course
         $courseInfo = api_get_course_info_by_id($courseId);
         if (!empty($courseInfo)) {
             $this->course = $courseInfo;

+ 4 - 0
main/inc/lib/api.lib.php

@@ -7237,6 +7237,10 @@ function api_can_login_as($loginAsUserId, $userId = null)
  */
 function api_is_allowed_in_course()
 {
+    if (api_is_platform_admin()) {
+        return true;
+    }
+
     return Session::read('is_allowed_in_course');
 }
 

+ 1 - 1
main/inc/lib/course_home.lib.php

@@ -505,7 +505,7 @@ class CourseHome
                 //Other queries recover id, name, link, image, visibility, admin, address, added_tool, target, category and session_id
                 // but plugins are not present in the tool table, only globally and inside the course_settings table once configured
                 $sql = "SELECT * FROM $course_tool_table t
-                        WHERE category = 'plugin' AND c_id = $course_id $condition_session
+                        WHERE category = 'plugin' AND name <> 'courseblock' AND c_id = $course_id $condition_session
                         ORDER BY id";
                 $result = Database::query($sql);
                 break;

+ 64 - 53
main/inc/lib/fileUpload.lib.php

@@ -77,8 +77,11 @@ function process_uploaded_file($uploaded_file, $show_output = true)
             case 1:
                 // The uploaded file exceeds the upload_max_filesize directive in php.ini.
                 if ($show_output) {
-                    Display::display_error_message(
-                        get_lang('UplExceedMaxServerUpload').ini_get('upload_max_filesize')
+                    Display::addFlash(
+                        Display::return_message(
+                            get_lang('UplExceedMaxServerUpload').ini_get('upload_max_filesize'),
+                            'error'
+                        )
                     );
                 }
 
@@ -89,21 +92,36 @@ function process_uploaded_file($uploaded_file, $show_output = true)
                 // (e.g. image upload in html editor).
                 $max_file_size = intval($_POST['MAX_FILE_SIZE']);
                 if ($show_output) {
-                    Display::display_error_message(get_lang('UplExceedMaxPostSize'). format_file_size($max_file_size));
+                    Display::addFlash(
+                        Display::return_message(
+                            get_lang('UplExceedMaxPostSize'). format_file_size($max_file_size),
+                            'error'
+                        )
+                    );
                 }
 
                 return false;
             case 3:
                 // The uploaded file was only partially uploaded.
                 if ($show_output) {
-                    Display::display_error_message(get_lang('UplPartialUpload').' '.get_lang('PleaseTryAgain'));
+                    Display::addFlash(
+                        Display::return_message(
+                            get_lang('UplPartialUpload').' '.get_lang('PleaseTryAgain'),
+                            'error'
+                        )
+                    );
                 }
 
                 return false;
             case 4:
                 // No file was uploaded.
                 if ($show_output) {
-                    Display::display_error_message(get_lang('UplNoFileUploaded').' '. get_lang('UplSelectFileFirst'));
+                    Display::addFlash(
+                        Display::return_message(
+                            get_lang('UplNoFileUploaded').' '. get_lang('UplSelectFileFirst'),
+                            'error'
+                        )
+                    );
                 }
 
                 return false;
@@ -113,7 +131,7 @@ function process_uploaded_file($uploaded_file, $show_output = true)
     if (!file_exists($uploaded_file['tmp_name'])) {
         // No file was uploaded.
         if ($show_output) {
-            Display::display_error_message(get_lang('UplUploadFailed'));
+            Display::addFlash(Display::return_message(get_lang('UplUploadFailed'), 'error'));
         }
         return false;
     }
@@ -123,7 +141,12 @@ function process_uploaded_file($uploaded_file, $show_output = true)
         if (empty($filesize)) {
             // No file was uploaded.
             if ($show_output) {
-                Display::display_error_message(get_lang('UplUploadFailedSizeIsZero'));
+                Display::addFlash(
+                    Display::return_message(
+                        get_lang('UplUploadFailedSizeIsZero'),
+                        'error'
+                    )
+                );
             }
 
             return false;
@@ -131,14 +154,19 @@ function process_uploaded_file($uploaded_file, $show_output = true)
     }
 
     $course_id = api_get_course_id();
-    //Checking course quota if we are in a course
 
+    //Checking course quota if we are in a course
     if (!empty($course_id)) {
         $max_filled_space = DocumentManager::get_course_quota();
         // Check if there is enough space to save the file
         if (!DocumentManager::enough_space($uploaded_file['size'], $max_filled_space)) {
             if ($show_output) {
-                Display::display_error_message(get_lang('UplNotEnoughSpace'));
+                Display::addFlash(
+                    Display::return_message(
+                        get_lang('UplNotEnoughSpace'),
+                        'error'
+                    )
+                );
             }
 
             return false;
@@ -201,7 +229,6 @@ function handle_uploaded_document(
     }
 
     $userInfo = api_get_user_info();
-
     $uploadedFile['name'] = stripslashes($uploadedFile['name']);
     // Add extension to files without one (if possible)
     $uploadedFile['name'] = add_ext_on_mime($uploadedFile['name'], $uploadedFile['type']);
@@ -282,7 +309,10 @@ function handle_uploaded_document(
                 if (!mkdir($whereToSave, api_get_permissions_for_new_directories())) {
                     if ($output) {
                         Display::addFlash(
-                            Display::return_message(get_lang('DestDirectoryDoesntExist').' ('.$uploadPath.')', 'error')
+                            Display::return_message(
+                                get_lang('DestDirectoryDoesntExist').' ('.$uploadPath.')',
+                                'error'
+                            )
                         );
                     }
 
@@ -451,7 +481,6 @@ function handle_uploaded_document(
 
                             return $filePath;
                         } else {
-
                             // Put the document data in the database
                             $documentId = add_document(
                                 $courseInfo,
@@ -487,6 +516,7 @@ function handle_uploaded_document(
 
                             // If the file is in a folder, we need to update all parent folders
                             item_property_update_on_folder($courseInfo, $uploadPath, $userId);
+
                             // Display success message to user
                             if ($output) {
                                 Display::addFlash(
@@ -534,12 +564,10 @@ function handle_uploaded_document(
                     );
 
                     $documentTitle = get_document_title($cleanName);
-
                     $fullPath = $whereToSave.$fileSystemName;
                     $filePath = $uploadPath.$fileSystemName;
 
                     if (moveUploadedFile($uploadedFile, $fullPath)) {
-
                         chmod($fullPath, $filePermissions);
                         // Put the document data in the database
                         $documentId = add_document(
@@ -579,30 +607,25 @@ function handle_uploaded_document(
 
                         // Display success message to user
                         if ($output) {
-                            if (isset($_POST['moodle_import'])) {
-                                Display::addFlash(
-                                    Display::display_confirmation_message(
-                                        get_lang('UplUploadSucceeded') . '<br />' . get_lang('UplFileSavedAs') . ' ' . $documentTitle,
-                                        false,
-                                        true
-                                    )
-                                );
-                            } else {
-                                Display::display_confirmation_message(
+                            Display::addFlash(
+                                Display::return_message(
                                     get_lang('UplUploadSucceeded') . '<br />' . get_lang('UplFileSavedAs') . ' ' . $documentTitle,
+                                    'success',
                                     false
-                                );
-                            }
+                                )
+                            );
                         }
 
                         return $filePath;
                     } else {
                         if ($output) {
-                            if (isset($_POST['moodle_import'])) {
-                                Display::addFlash(Display::display_error_message(get_lang('UplUnableToSaveFile'), false, true));
-                            } else {
-                                Display::display_error_message(get_lang('UplUnableToSaveFile'));
-                            }
+                            Display::addFlash(
+                                Display::return_message(
+                                    get_lang('UplUnableToSaveFile'),
+                                    'error',
+                                    false
+                                )
+                            );
                         }
 
                         return false;
@@ -612,11 +635,9 @@ function handle_uploaded_document(
                     // Only save the file if it doesn't exist or warn user if it does exist
                     if (file_exists($fullPath) && $docId) {
                         if ($output) {
-                            if (isset($_POST['moodle_import'])) {
-                                Display::addFlash(Display::display_error_message($cleanName.' '.get_lang('UplAlreadyExists'), false, true));
-                            } else {
-                                Display::display_error_message($cleanName.' '.get_lang('UplAlreadyExists'));
-                            }
+                            Display::addFlash(
+                                Display::return_message($cleanName.' '.get_lang('UplAlreadyExists'), 'error', false)
+                            );
                         }
                     } else {
                         if (moveUploadedFile($uploadedFile, $fullPath)) {
@@ -663,23 +684,13 @@ function handle_uploaded_document(
 
                             // Display success message to user
                             if ($output) {
-                                if (isset($_POST['moodle_import'])) {
-                                    Display::addFlash(
-                                        Display::display_confirmation_message(
-                                            get_lang('UplUploadSucceeded') . '<br /> ' . $documentTitle,
-                                            false,
-                                            true
-                                        )
-                                    );
-                                } else {
-                                    Display::addFlash(
-                                        Display::return_message(
-                                            get_lang('UplUploadSucceeded').'<br /> '.$documentTitle,
-                                            'confirmation',
-                                            false
-                                        )
-                                    );
-                                }
+                                Display::addFlash(
+                                    Display::display_confirmation_message(
+                                        get_lang('UplUploadSucceeded') . '<br /> ' . $documentTitle,
+                                        false,
+                                        true
+                                    )
+                                );
                             }
 
                             return $filePath;

+ 1 - 0
main/inc/lib/formvalidator/FormValidator.class.php

@@ -832,6 +832,7 @@ EOT;
                 </div>'
             );
             $this->addHidden($id.'_crop_result', '');
+            $this->addHidden($id.'_crop_image_base_64', '');
         }
     }
 

+ 10 - 30
main/inc/lib/pear/HTML/QuickForm/file.php

@@ -1,5 +1,4 @@
 <?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
 
 /**
  * HTML class for a file upload field
@@ -36,17 +35,12 @@
  */
 class HTML_QuickForm_file extends HTML_QuickForm_input
 {
-    // {{{ properties
-
    /**
     * Uploaded file data, from $_FILES
     * @var array
     */
     var $_value = null;
 
-    // }}}
-    // {{{ constructor
-
     /**
      * Class constructor
      *
@@ -61,12 +55,7 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
     {
         parent::__construct($elementName, $elementLabel, $attributes);
         $this->setType('file');
-
-
-    } //end constructor
-
-    // }}}
-    // {{{ setSize()
+    }
 
     /**
      * Sets size of file element
@@ -78,10 +67,7 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
     function setSize($size)
     {
         $this->updateAttributes(array('size' => $size));
-    } //end func setSize
-
-    // }}}
-    // {{{ getSize()
+    }
 
     /**
      * Returns size of file element
@@ -93,10 +79,7 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
     function getSize()
     {
         return $this->getAttribute('size');
-    } //end func getSize
-
-    // }}}
-    // {{{ freeze()
+    }
 
     /**
      * Freeze the element so that only its value is returned
@@ -107,10 +90,7 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
     function freeze()
     {
         return false;
-    } //end func freeze
-
-    // }}}
-    // {{{ setValue()
+    }
 
     /**
      * Sets value for file element.
@@ -128,10 +108,7 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
     function setValue($value)
     {
         return null;
-    } //end func setValue
-
-    // }}}
-    // {{{ getValue()
+    }
 
     /**
      * Returns information about the uploaded file
@@ -323,12 +300,16 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
                 $image.attr("src", dataUrl);
                 $image.cropper("destroy");
                 $cropButton.addClass("hidden");
+                $("[name='.$id.'_crop_image_base_64]").val($("#'.$id.'_preview_image").attr("src"));
                 return false;
             });
         });
         </script>';
     }
 
+    /**
+     * @return string
+     */
     public function toHtml()
     {
         $js = '';
@@ -345,6 +326,5 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
         } else {
             return $js.$this->_getTabs() . '<input' . $this->_getAttrString($this->_attributes) . ' />';
         }
-    } //end func toHtml
-
+    }
 }

+ 6 - 0
main/inc/lib/plugin.class.php

@@ -26,6 +26,8 @@ class Plugin
     public $isCoursePlugin = false;
     public $isAdminPlugin = false;
     public $isMailPlugin = false;
+    // Adds icon in the course home
+    public $addCourseTool = true;
 
     /**
      * When creating a new course, these settings are added to the course, in
@@ -461,6 +463,10 @@ class Plugin
             return true;
         }
 
+        if ($this->addCourseTool == false) {
+            return true;
+        }
+
         //Add an icon in the table tool list
         $table = Database::get_course_table(TABLE_TOOL_LIST);
         $sql = "SELECT name FROM $table

+ 22 - 0
main/inc/lib/sessionmanager.lib.php

@@ -8064,4 +8064,26 @@ class SessionManager
 
         return $htmlRes;
     }
+
+    /**
+     * @param int $userId
+     * @param int $courseId
+     *
+     * @return array
+     */
+    public static function searchCourseInSessionsFromUser($userId, $courseId)
+    {
+        $table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
+        $userId = (int) $userId;
+        $courseId = (int) $courseId;
+        if (empty($userId) || empty($courseId)) {
+            return [];
+        }
+
+        $sql = "SELECT * FROM $table 
+                WHERE c_id = $courseId AND user_id = $userId";
+        $result = Database::query($sql);
+
+        return Database::store_result($result, 'ASSOC');
+    }
 }

+ 1 - 1
main/inc/lib/tracking.lib.php

@@ -963,7 +963,7 @@ class Tracking
                                                 } else {
                                                     $output .= '<td>
                                                             <a href="../exercise/exercise_show.php?origin=' . $origin . '&id=' . $my_exe_id . '&cidReq=' . $courseCode . '" target="_parent">
-                                                            <img src="' . Display::returnIconPath('quiz.gif').'" alt="' . get_lang('ShowAttempt') . '" title="' . get_lang('ShowAttempt') . '">
+                                                            <img src="' . Display::returnIconPath('quiz.png').'" alt="' . get_lang('ShowAttempt') . '" title="' . get_lang('ShowAttempt') . '">
                                                             </a></td>';
                                                 }
                                             } else {

+ 28 - 2
main/inc/local.inc.php

@@ -1312,9 +1312,35 @@ if ((isset($uidReset) && $uidReset) || (isset($cidReset) && $cidReset)) {
                 }
             }
 
-            //If I'm the admin platform i'm a teacher of the course
+            // If I'm the admin platform i'm a teacher of the course
             if ($is_platformAdmin) {
-                $is_courseAdmin     = true;
+                $is_courseAdmin = true;
+            }
+        } else {
+            // User has not access to the course
+            // This will check if the course was added in one of his sessions
+            // Then it will be redirected to that course-session
+            if ($is_courseMember == false) {
+                // Search session
+                $courseSession = SessionManager::searchCourseInSessionsFromUser(
+                    $user_id,
+                    $_course['real_id']
+                );
+
+                if (!empty($courseSession) && isset($courseSession[0])) {
+                    $courseSessionItem = $courseSession[0];
+                    if (isset($courseSessionItem['session_id'])) {
+                        $customSessionId = $courseSessionItem['session_id'];
+                        $url = $_course['course_public_url'].'?id_session='.$customSessionId;
+
+                        Session::erase('_real_cid');
+                        Session::erase('_cid');
+                        Session::erase('_course');
+
+                        header('Location: '.$url);
+                        exit;
+                    }
+                }
             }
         }
     } else { // keys missing => not anymore in the course - user relation

+ 1 - 1
main/mySpace/myStudents.php

@@ -1151,7 +1151,7 @@ if (!empty($student_id)) {
                     $id_last_attempt = Database :: result($result_last_attempt, 0, 0);
                     if ($count_attempts > 0)
                         echo '<a href="../exercise/exercise_show.php?id=' . $id_last_attempt . '&cidReq='.$course_code.'&session_id='.$sessionId.'&student='.$student_id.'&origin='.(empty($origin)?'tracking':$origin).'">
-                        '.Display::return_icon('quiz.gif').'
+                        '.Display::return_icon('quiz.png').'
                      </a>';
                 }
                 echo '</td>';

+ 1 - 1
main/session/index.php

@@ -342,7 +342,7 @@ foreach ($final_array as $session_data) {
 
                                 $my_real_array[] = array(
                                     'status' => Display::return_icon(
-                                        'quiz.gif',
+                                        'quiz.png',
                                         get_lang('Attempted'),
                                         '',
                                         ICON_SIZE_SMALL

+ 1 - 1
main/tracking/course_log_tools.php

@@ -207,7 +207,7 @@ if ($exerciseReporting) {
     echo '<div class="report_section">
                '.Display::page_subheader(
             Display::return_icon(
-                'quiz.gif',
+                'quiz.png',
                 get_lang('AverageResultsToTheExercices')
             ).get_lang('AverageResultsToTheExercices')
         ).'

+ 1 - 1
main/user/user.php

@@ -447,7 +447,7 @@ if (api_is_allowed_to_edit(null, true)) {
 }
 
 // $is_allowed_in_course is first defined in local.inc.php
-if (!$is_allowed_in_course) {
+if (!api_is_allowed_in_course()) {
     api_not_allowed(true);
 }
 

+ 63 - 0
plugin/buycourses/database.php

@@ -209,6 +209,69 @@ $saleTable->addForeignKeyConstraint(
     ['onDelete' => 'CASCADE']
 );
 
+$servicesTable = $pluginSchema->createTable(BuyCoursesPlugin::TABLE_SERVICES);
+$servicesTable->addColumn(
+    'id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['autoincrement' => true, 'unsigned' => true]
+);
+$servicesTable->addColumn('name', \Doctrine\DBAL\Types\Type::STRING);
+$servicesTable->addColumn('description', \Doctrine\DBAL\Types\Type::TEXT);
+$servicesTable->addColumn(
+    'price',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2]
+);
+$servicesTable->addColumn('duration_days', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('applies_to', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('owner_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('visibility', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesTable->addColumn('video_url', \Doctrine\DBAL\Types\Type::STRING);
+$servicesTable->addColumn('image', \Doctrine\DBAL\Types\Type::STRING);
+$servicesTable->addColumn('service_information', \Doctrine\DBAL\Types\Type::TEXT);
+$servicesTable->setPrimaryKey(['id']);
+
+$servicesNodeTable = $pluginSchema->createTable(BuyCoursesPlugin::TABLE_SERVICES_SALE);
+$servicesNodeTable->addColumn(
+    'id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['autoincrement' => true, 'unsigned' => true]
+);
+$servicesNodeTable->addColumn(
+    'service_id',
+    \Doctrine\DBAL\Types\Type::INTEGER,
+    ['unsigned' => true]
+);
+$servicesNodeTable->addColumn('reference', \Doctrine\DBAL\Types\Type::STRING);
+$servicesNodeTable->addColumn('currency_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn(
+    'price',
+    \Doctrine\DBAL\Types\Type::DECIMAL,
+    ['scale' => 2]
+);
+$servicesNodeTable->addColumn('node_type', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('node_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('buyer_id', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('buy_date', \Doctrine\DBAL\Types\Type::DATETIME);
+$servicesNodeTable->addColumn(
+    'date_start',
+    \Doctrine\DBAL\Types\Type::DATETIME,
+    ['notnull' => false]
+);
+$servicesNodeTable->addColumn(
+    'date_end',
+    \Doctrine\DBAL\Types\Type::DATETIME
+);
+$servicesNodeTable->addColumn('status', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->addColumn('payment_type', \Doctrine\DBAL\Types\Type::INTEGER);
+$servicesNodeTable->setPrimaryKey(['id']);
+$servicesNodeTable->addForeignKeyConstraint(
+    $servicesTable,
+    ['service_id'],
+    ['id'],
+    ['onDelete' => 'CASCADE']
+);
+
 $queries = $pluginSchema->toSql($platform);
 
 foreach ($queries as $query) {

+ 15 - 1
plugin/buycourses/lang/spanish.php

@@ -39,7 +39,7 @@ $strings['Buyer'] = "Comprador";
 $strings['BankTransfer'] = "Transferencia Bancaria";
 $strings['SaleInfo'] = "Información de la venta";
 $strings['SaleStatusPending'] = "Venta pendiente";
-$strings['SaleStatusCanceled'] = "Venta cancelada";
+$strings['SaleStatusCancelled'] = "Venta cancelada";
 $strings['SaleStatusCompleted'] = "Venta completada";
 $strings['PayoutStatusPending'] = "Pago pendiente";
 $strings['PayoutStatusCanceled'] = "Pago cancelado";
@@ -133,3 +133,17 @@ $strings['ByUser'] = "Por usuario";
 $strings['PaymentMethod'] = "Método de pago";
 $strings['SWIFT'] = "Código SWIFT";
 $strings['SWIFT_help'] = "Formato estándar de los Códigos de Identificación Bancaria (BIC) que sirve como identificador único para un banco o institución financiera.";
+$strings['include_services'] = "Incluir Servicios";
+$strings['Services'] = "Servicios";
+$strings['Service'] = "Servicio";
+$strings['NewService'] = "Nuevo servicio";
+$strings['ServiceName'] = "Nombre de servicio";
+$strings['AppliesTo'] = "Aplicado a";
+$strings['ServiceInformation'] = "Información del servicio";
+$strings['EditService'] = "Editar servicio";
+$strings['ServiceAdded'] = "Servicio agregado";
+$strings['ServiceEdited'] = "Servicio editado";
+$strings['ServiceSaleInfo'] = "Información del servicio";
+$strings['ListOfServicesOnSale'] = "Lista de servicios a la venta";
+$strings['AdditionalInfo'] = "Información adicional";
+$strings['PleaseSelectTheCorrectInfoToApplyTheService'] = "Porfavor Seleccione la información correcta para aplicar el servicio";

+ 30 - 1
plugin/buycourses/resources/css/style.css

@@ -1,5 +1,34 @@
 .buy-courses-tabs {
-    margin-bottom: 15px;
+  margin-bottom: 15px;
+}
+
+.buy-courses-page-header {
+  padding-bottom: 9px;
+  margin: 40px 0 20px;
+  border-bottom: 1px solid #eee;
+}
+
+.buy-courses-block-button {
+  display: block;
+  width: 100%;
+  height: 50px;
+  line-height: 35px;
+  font-size: 16px;
+}
+
+.buy-courses-cross-out {
+  text-decoration:line-through;
+}
+
+.buy-courses-title-color {
+  color: #5DC3E1;
+  padding-bottom: 10px;
+}
+
+.buy-courses-description-service {
+  text-align: justify;
+  font-size: 18px;
+  line-height: 1.42857;
 }
 
 @-moz-keyframes wobblebar-loader {

+ 426 - 3
plugin/buycourses/src/buy_course_plugin.class.php

@@ -23,6 +23,8 @@ class BuyCoursesPlugin extends Plugin
     const TABLE_TRANSFER = 'plugin_buycourses_transfer';
     const TABLE_COMMISSION = 'plugin_buycourses_commission';
     const TABLE_PAYPAL_PAYOUTS = 'plugin_buycourses_paypal_payouts';
+    const TABLE_SERVICES = 'plugin_buycourses_services';
+    const TABLE_SERVICES_SALE = 'plugin_buycourses_service_sale';
     const PRODUCT_TYPE_COURSE = 1;
     const PRODUCT_TYPE_SESSION = 2;
     const PAYMENT_TYPE_PAYPAL = 1;
@@ -33,6 +35,12 @@ class BuyCoursesPlugin extends Plugin
     const SALE_STATUS_CANCELED = -1;
     const SALE_STATUS_PENDING = 0;
     const SALE_STATUS_COMPLETED = 1;
+    const SERVICE_STATUS_PENDING = 0;
+    const SERVICE_STATUS_COMPLETED = 1;
+    const SERVICE_STATUS_CANCELLED = -1;
+    const SERVICE_TYPE_USER = 1;
+    const SERVICE_TYPE_COURSE = 2;
+    const SERVICE_TYPE_SESSION = 3;
 
     /**
      *
@@ -54,11 +62,12 @@ class BuyCoursesPlugin extends Plugin
                 Alex Aragón - BeezNest (Design icons and css styles) <br/>
                 Imanol Losada - BeezNest (introduction of sessions purchase) <br/>
                 Angel Fernando Quiroz Campos - BeezNest (cleanup and new reports) <br/>
-                José Loguercio Silva - BeezNest (pay teachers and commissions)
+                José Loguercio Silva - BeezNest (Payouts and buy Services)
             ",
             array(
                 'show_main_menu_tab' => 'boolean',
                 'include_sessions' => 'boolean',
+                'include_services' => 'boolean',
                 'paypal_enable' => 'boolean',
                 'transfer_enable' => 'boolean',
                 'commissions_enable' => 'boolean',
@@ -80,7 +89,9 @@ class BuyCoursesPlugin extends Plugin
             self::TABLE_SALE,
             self::TABLE_CURRENCY,
             self::TABLE_COMMISSION,
-            self::TABLE_PAYPAL_PAYOUTS
+            self::TABLE_PAYPAL_PAYOUTS,
+            self::TABLE_SERVICES,
+            self::TABLE_SERVICES_SALE
         );
         $em = Database::getManager();
         $cn = $em->getConnection();
@@ -107,7 +118,9 @@ class BuyCoursesPlugin extends Plugin
             self::TABLE_SALE,
             self::TABLE_CURRENCY,
             self::TABLE_COMMISSION,
-            self::TABLE_PAYPAL_PAYOUTS
+            self::TABLE_PAYPAL_PAYOUTS,
+            self::TABLE_SERVICES_SALE,
+            self::TABLE_SERVICES
         );
 
         foreach ($tablesToBeDeleted as $tableToBeDeleted) {
@@ -1663,4 +1676,414 @@ class BuyCoursesPlugin extends Plugin
         );
     }
 
+    /**
+     * Register addicional service
+     * @param array params $service
+     * @return mixed response
+     */
+    public function storeService($service)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+
+        $return = Database::insert(
+            $servicesTable,
+            [
+                'name' => Security::remove_XSS($service['name']),
+                'description' => Security::remove_XSS($service['description']),
+                'price' => $service['price'],
+                'duration_days' => intval($service['duration_days']),
+                'applies_to' => intval($service['applies_to']),
+                'owner_id' => intval($service['owner_id']),
+                'visibility' => intval($service['visibility']),
+                'image' => 'simg.png',
+                'video_url' => $service['video_url'],
+                'service_information' => $service['service_information']
+            ]
+        );
+
+        if ($return) {
+            $img = str_replace('data:image/png;base64,', '', $service['picture_crop_image_base_64']);
+            $img = str_replace(' ', '+', $img);
+            $data = base64_decode($img);
+            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$return.'.png';
+            file_put_contents($file, $data);
+
+            Database::update(
+                $servicesTable,
+                ['image' => 'simg-'.$return.'.png'],
+                ['id = ?' => intval($return)]
+            );
+            return $return;
+        }
+
+        return false;
+    }
+
+    /**
+     * update a service
+     * @param array $service
+     * @param integer $id
+     * @return mixed response
+     */
+    public function updateService($service, $id)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+        if (!empty($service['picture_crop_image_base_64'])) {
+            $img = str_replace('data:image/png;base64,', '', $service['picture_crop_image_base_64']);
+            $img = str_replace(' ', '+', $img);
+            $data = base64_decode($img);
+            $file = api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$id.'.png';
+            file_put_contents($file, $data);
+        }
+
+        return Database::update(
+            $servicesTable,
+            [
+                'name' => Security::remove_XSS($service['name']),
+                'description' => Security::remove_XSS($service['description']),
+                'price' => $service['price'],
+                'duration_days' => intval($service['duration_days']),
+                'applies_to' => intval($service['applies_to']),
+                'owner_id' => intval($service['owner_id']),
+                'visibility' => intval($service['visibility']),
+                'image' => 'simg-'.$id.'.png',
+                'video_url' => $service['video_url'],
+                'service_information' => $service['service_information']
+            ],
+            ['id = ?' => intval($id)]
+        );
+    }
+
+    /**
+     * Remove a service
+     * @param int $id The transfer account ID
+     * @return int Rows affected. Otherwise return false
+     */
+    public function deleteService($id)
+    {
+        return Database::delete(
+            Database::get_main_table(self::TABLE_SERVICES),
+            ['id = ?' => intval($id)]
+        );
+    }
+
+    /**
+     * List adicional services
+     * @param integer $id service id
+     * @return array
+     */
+    public function getServices($id = null)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+        $userTable = Database::get_main_table(TABLE_MAIN_USER);
+
+        $conditions = null;
+        $showData = "all";
+
+        if ($id) {
+            $conditions = ['WHERE' => ['s.id = ?' => $id]];
+            $showData = "first";
+        }
+
+        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
+        $currency = $this->getSelectedCurrency();
+        $isoCode = $currency['iso_code'];
+        $return = Database::select(
+            "s.*, '$isoCode' as currency, u.firstname, u.lastname",
+            "$servicesTable s $innerJoins",
+            $conditions,
+            $showData
+        );
+
+        $services = [];
+
+        if ($id) {
+            $services['id'] = $return['id'];
+            $services['name'] = $return['name'];
+            $services['description'] = $return['description'];
+            $services['price'] = $return['price'];
+            $services['currency'] = $return['currency'];
+            $services['duration_days'] = $return['duration_days'];
+            $services['applies_to'] = $return['applies_to'];
+            $services['owner_id'] = $return['owner_id'];
+            $services['owner_name'] = api_get_person_name($return['firstname'], $return['lastname']);
+            $services['visibility'] = $return['visibility'];
+            $services['image'] = $return['image'];
+            $services['video_url'] = $return['video_url'];
+            $services['service_information'] = $return['service_information'];
+
+            return $services;
+        }
+
+        foreach ($return as $index => $service) {
+            $services[$index]['id'] = $service['id'];
+            $services[$index]['name'] = $service['name'];
+            $services[$index]['description'] = $service['description'];
+            $services[$index]['price'] = $service['price'];
+            $services[$index]['currency'] = $service['currency'];
+            $services[$index]['duration_days'] = $service['duration_days'];
+            $services[$index]['applies_to'] = $service['applies_to'];
+            $services[$index]['owner_id'] = $service['owner_id'];
+            $services[$index]['owner_name'] = api_get_person_name($service['firstname'], $service['lastname']);
+            $services[$index]['visibility'] = $service['visibility'];
+            $services[$index]['image'] = $service['image'];
+            $services[$index]['video_url'] = $service['video_url'];
+            $services[$index]['service_information'] = $service['service_information'];
+        }
+
+        return $services;
+    }
+
+    /**
+     * Get the statuses for sales
+     * @return array
+     */
+    public function getServiceSaleStatuses()
+    {
+        return [
+            self::SERVICE_STATUS_CANCELLED => $this->get_lang('SaleStatusCancelled'),
+            self::SERVICE_STATUS_PENDING => $this->get_lang('SaleStatusPending'),
+            self::SERVICE_STATUS_COMPLETED => $this->get_lang('SaleStatusCompleted')
+        ];
+    }
+
+    /**
+     * List services sales
+     * @param integer $id service id
+     * @param integer $buyerId buyer id
+     * @param integer $status status
+     * @param integer $nodeType The node Type ( User = 1 , Course = 2 , Session = 3 )
+     * @param integer $nodeId the nodeId
+     * @param boolean $hot enable hot services
+     * @return array
+     */
+    public function getServiceSale($id = null, $buyerId = null, $status = null, $nodeType = null, $nodeId = null, $hot = false)
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+        $servicesSaleTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES_SALE);
+
+        $conditions = null;
+        $showData = "all";
+        $groupBy = "";
+
+        if ($id) {
+            $conditions = ['WHERE' => ['ss.id = ?' => $id]];
+            $showData = "first";
+        }
+
+        if ($buyerId) {
+            $conditions = ['WHERE' => ['ss.buyer_id = ?' => $buyerId], 'ORDER' => 'id ASC'];
+        }
+
+        if (is_numeric($status)) {
+            $conditions = ['WHERE' => ['ss.status = ?' => $status], 'ORDER' => 'id ASC'];
+        }
+
+        if ($id && $buyerId) {
+            $conditions = ['WHERE' => ['ss.id = ? AND ss.buyer_id = ?' => [$id, $buyerId]], 'ORDER' => 'id ASC'];
+        }
+
+        if ($nodeType && $nodeId) {
+            $conditions = ['WHERE' => ['ss.node_type = ? AND ss.node_id = ?' => [$nodeType, $nodeId]], 'ORDER' => 'id ASC'];
+        }
+
+        if ($hot) {
+            $hot = "count(ss.service_id) as hot, ";
+            $conditions = ['ORDER' => 'hot DESC', 'LIMIT' => '6'];
+            $groupBy = "GROUP BY ss.service_id";
+            "clean_teacher_files.php";
+        }
+
+        $innerJoins = "INNER JOIN $servicesTable s ON ss.service_id = s.id $groupBy";
+        $currency = $this->getSelectedCurrency();
+        $isoCode = $currency['iso_code'];
+        $return = Database::select(
+            "ss.*, s.name, s.description, s.price as service_price, s.duration_days, s.applies_to, s.owner_id, s.visibility, s.image, $hot '$isoCode' as currency",
+            "$servicesSaleTable ss $innerJoins",
+            $conditions,
+            $showData
+        );
+
+        $servicesSale = [];
+
+        if ($id) {
+
+            $owner = api_get_user_info($return['owner_id']);
+            $buyer = api_get_user_info($return['buyer_id']);
+
+            $servicesSale['id'] = $return['id'];
+            $servicesSale['service']['id'] = $return['service_id'];
+            $servicesSale['service']['name'] = $return['name'];
+            $servicesSale['service']['description'] = $return['description'];
+            $servicesSale['service']['price'] = $return['service_price'];
+            $servicesSale['service']['duration_days'] = $return['duration_days'];
+            $servicesSale['service']['applies_to'] = $return['applies_to'];
+            $servicesSale['service']['owner']['id'] = $return['owner_id'];
+            $servicesSale['service']['owner']['name'] = api_get_person_name($owner['firstname'], $owner['lastname']);
+            $servicesSale['service']['visibility'] = $return['visibility'];
+            $servicesSale['service']['image'] = $return['image'];
+            $servicesSale['reference'] = $return['reference'];
+            $servicesSale['currency_id'] = $return['currency_id'];
+            $servicesSale['currency'] = $return['currency'];
+            $servicesSale['price'] = $return['price'];
+            $servicesSale['node_type'] = $return['node_type'];
+            $servicesSale['node_id'] = $return['node_id'];
+            $servicesSale['buyer']['id'] = $buyer['user_id'];
+            $servicesSale['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
+            $servicesSale['buyer']['username'] = $buyer['username'];
+            $servicesSale['buy_date'] = $return['buy_date'];
+            $servicesSale['date_start'] = $return['date_start'];
+            $servicesSale['date_end'] = $return['date_end'];
+            $servicesSale['status'] = $return['status'];
+            $servicesSale['payment_type'] = $return['payment_type'];
+
+            return $servicesSale;
+        }
+
+
+        foreach ($return as $index => $service) {
+
+            $owner = api_get_user_info($service['owner_id']);
+            $buyer = api_get_user_info($service['buyer_id']);
+
+            $servicesSale[$index]['id'] = $service['id'];
+            $servicesSale[$index]['service']['id'] = $service['service_id'];
+            $servicesSale[$index]['service']['name'] = $service['name'];
+            $servicesSale[$index]['service']['description'] = $service['description'];
+            $servicesSale[$index]['service']['price'] = $service['service_price'];
+            $servicesSale[$index]['service']['duration_days'] = $service['duration_days'];
+            $servicesSale[$index]['service']['applies_to'] = $service['applies_to'];
+            $servicesSale[$index]['service']['owner']['id'] = $service['owner_id'];
+            $servicesSale[$index]['service']['owner']['name'] = api_get_person_name($owner['firstname'], $owner['lastname']);
+            $servicesSale[$index]['service']['visibility'] = $service['visibility'];
+            $servicesSale[$index]['service']['image'] = $service['image'];
+            $servicesSale[$index]['reference'] = $service['reference'];
+            $servicesSale[$index]['currency_id'] = $service['currency_id'];
+            $servicesSale[$index]['currency'] = $service['currency'];
+            $servicesSale[$index]['price'] = $service['price'];
+            $servicesSale[$index]['node_type'] = $service['node_type'];
+            $servicesSale[$index]['node_id'] = $service['node_id'];
+            $servicesSale[$index]['buyer']['id'] = $service['buyer_id'];
+            $servicesSale[$index]['buyer']['name'] = api_get_person_name($buyer['firstname'], $buyer['lastname']);
+            $servicesSale[$index]['buyer']['username'] = $buyer['username'];
+            $servicesSale[$index]['buy_date'] = $service['buy_date'];
+            $servicesSale[$index]['date_start'] = $service['date_start'];
+            $servicesSale[$index]['date_end'] = $service['date_end'];
+            $servicesSale[$index]['status'] = $service['status'];
+            $servicesSale[$index]['payment_type'] = $service['payment_type'];
+        }
+
+        return $servicesSale;
+    }
+
+    /**
+     * Update service sale status to cancelled
+     * @param int $serviceSaleId The sale ID
+     * @return boolean
+     */
+    public function cancelServiceSale($serviceSaleId)
+    {
+        $this->updateServiceSaleStatus($serviceSaleId, self::SERVICE_STATUS_CANCELLED);
+        return true;
+    }
+
+    /**
+     * Complete service sale process. Update service sale status to completed
+     * @param int $serviceSaleId The service sale ID
+     * @return boolean
+     */
+    public function completeServiceSale($serviceSaleId)
+    {
+        $serviceSale = $this->getServiceSale($serviceSaleId);
+
+        if ($serviceSale['status'] == self::SERVICE_STATUS_COMPLETED) {
+            return true;
+        }
+
+        $this->updateServiceSaleStatus($serviceSaleId, self::SERVICE_STATUS_COMPLETED);
+
+        return true;
+    }
+
+    /**
+     * Lists current service details
+     * @param string $name Optional. The name filter
+     * @param int $min Optional. The minimum price filter
+     * @param int $max Optional. The maximum price filter
+     * @param mixed $appliesTo Optional.
+     * @return array
+     */
+    public function getCatalogServiceList($name = null, $min = 0, $max = 0, $appliesTo = '')
+    {
+        $servicesTable = Database::get_main_table(BuyCoursesPlugin::TABLE_SERVICES);
+        $userTable = Database::get_main_table(TABLE_MAIN_USER);
+
+        $whereConditions = [
+            's.id <> ? ' => 0
+        ];
+
+        if (!empty($name)) {
+            $whereConditions['AND s.name LIKE %?%'] = $name;
+        }
+
+        if (!empty($min)) {
+            $whereConditions['AND s.price >= ?'] = $min;
+        }
+
+        if (!empty($max)) {
+            $whereConditions['AND s.price <= ?'] = $max;
+        }
+
+        if (!$appliesTo == '') {
+            $whereConditions['AND s.applies_to = ?'] = $appliesTo;
+        }
+
+        $innerJoins = "INNER JOIN $userTable u ON s.owner_id = u.id";
+        $currency = $this->getSelectedCurrency();
+        $isoCode = $currency['iso_code'];
+        $return = Database::select(
+            "s.*, '$isoCode' as currency, u.firstname, u.lastname",
+            "$servicesTable s $innerJoins",
+            ['WHERE' => $whereConditions]
+        );
+
+        $services = [];
+
+        foreach ($return as $index => $service) {
+            $services[$index]['id'] = $service['id'];
+            $services[$index]['name'] = $service['name'];
+            $services[$index]['description'] = $service['description'];
+            $services[$index]['price'] = $service['price'];
+            $services[$index]['currency'] = $service['currency'];
+            $services[$index]['duration_days'] = $service['duration_days'];
+            $services[$index]['applies_to'] = $service['applies_to'];
+            $services[$index]['owner_id'] = $service['owner_id'];
+            $services[$index]['owner_name'] = api_get_person_name($service['firstname'], $service['lastname']);
+            $services[$index]['visibility'] = $service['visibility'];
+            $services[$index]['image'] = api_get_path(WEB_PLUGIN_PATH).'buycourses/uploads/services/images/'.$service['image'];
+            $services[$index]['video_url'] = $service['video_url'];
+            $services[$index]['service_information'] = $service['service_information'];
+        }
+
+        return $services;
+
+    }
+
+    /**
+     * Update the service sale status
+     * @param int $serviceSaleId The service sale ID
+     * @param int $newStatus The new status
+     * @return boolean
+     */
+    private function updateServiceSaleStatus($serviceSaleId, $newStatus = self::SERVICE_STATUS_PENDING)
+    {
+        $serviceSaleTable = Database::get_main_table(self::TABLE_SERVICES_SALE);
+
+        return Database::update(
+            $serviceSaleTable,
+            ['status' => intval($newStatus)],
+            ['id = ?' => intval($serviceSaleId)]
+        );
+    }
+
 }

+ 13 - 7
plugin/buycourses/src/configuration.php

@@ -13,6 +13,7 @@ require_once __DIR__.'/../../../main/inc/global.inc.php';
 
 $plugin = BuyCoursesPlugin::create();
 $includeSession = $plugin->get('include_sessions') === 'true';
+$includeServices = $plugin->get('include_services') === 'true';
 
 api_protect_admin_script(true);
 
@@ -20,22 +21,21 @@ Display::addFlash(Display::return_message(get_lang('Info').' - '.$plugin->get_la
 
 $courses = $plugin->getCoursesForConfiguration();
 
-//view
+// breadcrumbs
 $interbreadcrumb[] = [
-    'url' => 'course_catalog.php',
-    'name' => $plugin->get_lang('CourseListOnSale')
-];
-$interbreadcrumb[] = [
-    'url' => 'paymentsetup.php',
-    'name' => get_lang('Configuration')
+    'url' => api_get_path(WEB_PLUGIN_PATH) . 'buycourses/index.php',
+    'name' => $plugin->get_lang('plugin_title')
 ];
 
 $templateName = $plugin->get_lang('AvailableCourses');
+
 $tpl = new Template($templateName);
+
 $tpl->assign('product_type_course', BuyCoursesPlugin::PRODUCT_TYPE_COURSE);
 $tpl->assign('product_type_session', BuyCoursesPlugin::PRODUCT_TYPE_SESSION);
 $tpl->assign('courses', $courses);
 $tpl->assign('sessions_are_included', $includeSession);
+$tpl->assign('services_are_included', $includeServices);
 
 if ($includeSession) {
     $sessions = $plugin->getSessionsForConfiguration();
@@ -43,6 +43,12 @@ if ($includeSession) {
     $tpl->assign('sessions', $sessions);
 }
 
+if ($includeServices) {
+    $services = $plugin->getServices();
+
+    $tpl->assign('services', $services);
+}
+
 $content = $tpl->fetch('buycourses/view/configuration.tpl');
 
 $tpl->assign('header', $templateName);

+ 2 - 0
plugin/buycourses/src/course_catalog.php

@@ -12,6 +12,7 @@ require_once __DIR__.'/../../../main/inc/global.inc.php';
 
 $plugin = BuyCoursesPlugin::create();
 $includeSessions = $plugin->get('include_sessions') === 'true';
+$includeServices = $plugin->get('include_services') === 'true';
 
 $nameFilter = null;
 $minFilter = 0;
@@ -68,6 +69,7 @@ $tpl->assign('search_filter_form', $form->returnForm());
 $tpl->assign('showing_courses', true);
 $tpl->assign('courses', $courseList);
 $tpl->assign('sessions_are_included', $includeSessions);
+$tpl->assign('services_are_included', $includeServices);
 
 $content = $tpl->fetch('buycourses/view/catalog.tpl');
 

文件差異過大導致無法顯示
+ 1 - 0
plugin/buycourses/src/culqi.php


+ 2 - 0
plugin/buycourses/src/sales_report.php

@@ -16,6 +16,7 @@ $plugin = BuyCoursesPlugin::create();
 
 $paypalEnable = $plugin->get('paypal_enable');
 $commissionsEnable = $plugin->get('commissions_enable');
+$includeServices = $plugin->get('include_services');
 
 if (isset($_GET['order'])) {
     $sale = $plugin->getSale($_GET['order']);
@@ -170,6 +171,7 @@ if ($commissionsEnable == "true") {
 $template->assign('form', $form->returnForm());
 $template->assign('selected_sale', $selectedSale);
 $template->assign('selected_status', $selectedStatus);
+$template->assign('services_are_included', $includeServices);
 $template->assign('sale_list', $saleList);
 $template->assign('sale_status_canceled', BuyCoursesPlugin::SALE_STATUS_CANCELED);
 $template->assign('sale_status_pending', BuyCoursesPlugin::SALE_STATUS_PENDING);

+ 89 - 0
plugin/buycourses/src/service_catalog.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * List of services
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Initialization
+ */
+
+$cidReset = true;
+
+require_once '../../../main/inc/global.inc.php';
+
+$plugin = BuyCoursesPlugin::create();
+$includeSessions = $plugin->get('include_sessions') === 'true';
+$includeServices = $plugin->get('include_services') === 'true';
+
+$nameFilter = null;
+$minFilter = 0;
+$maxFilter = 0;
+$appliesToFilter = '';
+
+$form = new FormValidator('search_filter_form', 'get', null, null, [], FormValidator::LAYOUT_INLINE);
+
+if ($form->validate()) {
+    $formValues = $form->getSubmitValues();
+    $nameFilter = isset($formValues['name']) ? $formValues['name'] : null;
+    $minFilter = isset($formValues['min']) ? $formValues['min'] : 0;
+    $maxFilter = isset($formValues['max']) ? $formValues['max'] : 0;
+    $appliesToFilter = isset($formValues['applies_to']) ? $formValues['applies_to'] : '';
+}
+
+$form->addHeader($plugin->get_lang('SearchFilter'));
+$form->addText('name', $plugin->get_lang('ServiceName'), false);
+$form->addElement(
+    'number',
+    'min',
+    $plugin->get_lang('MinimumPrice'),
+    ['step' => '0.01', 'min' => '0']
+);
+$form->addElement(
+    'number',
+    'max',
+    $plugin->get_lang('MaximumPrice'),
+    ['step' => '0.01', 'min' => '0']
+);
+$appliesToOptions = [
+    '' => get_lang('Any'),
+    0 => get_lang('None'),
+    1 => get_lang('User'),
+    2 => get_lang('Course'),
+    3 => get_lang('Session')
+];
+$form->addSelect('applies_to', $plugin->get_lang('AppliesTo'), $appliesToOptions);
+$form->addHtml('<hr>');
+$form->addButtonFilter(get_lang('Search'));
+
+$serviceList = $plugin->getCatalogServiceList($nameFilter, $minFilter, $maxFilter, $appliesToFilter);
+
+//View
+if (api_is_platform_admin()) {
+    $interbreadcrumb[] = [
+        'url' => 'configuration.php',
+        'name' => $plugin->get_lang('AvailableCoursesConfiguration')
+    ];
+    $interbreadcrumb[] = [
+        'url' => 'paymentsetup.php',
+        'name' => $plugin->get_lang('PaymentsConfiguration')
+    ];
+} else {
+    $interbreadcrumb[] = [
+        'url' => '../index.php',
+        'name' => $plugin->get_lang('UserPanel')
+    ];
+}
+
+$templateName = $plugin->get_lang('ListOfServicesOnSale');
+$tpl = new Template($templateName);
+$tpl->assign('search_filter_form', $form->returnForm());
+$tpl->assign('showing_services', true);
+$tpl->assign('services', $serviceList);
+$tpl->assign('sessions_are_included', $includeSessions);
+$tpl->assign('services_are_included', $includeServices);
+
+$content = $tpl->fetch('buycourses/view/catalog.tpl');
+
+$tpl->assign('header', $templateName);
+$tpl->assign('content', $content);
+$tpl->display_one_col_template();

+ 35 - 0
plugin/buycourses/src/service_error.php

@@ -0,0 +1,35 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Errors management for the Buy Courses plugin - Redirects to service_catalog.php with a error msg
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Config
+ */
+
+require_once '../config.php';
+
+if (isset($_SESSION['bc_service_sale_id'])) {
+
+    $plugin = BuyCoursesPlugin::create();
+    $serviceSaleId = $_SESSION['bc_service_sale_id'];
+    unset($_SESSION['bc_service_sale_id']);
+    $serviceSale = $plugin->getServiceSale($serviceSaleId);
+
+    $plugin->cancelServiceSale(intval($serviceSaleId));
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('OrderCancelled'), 'error', false)
+    );
+
+    header('Location: '. api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_catalog.php');
+    exit;
+}
+
+Display::addFlash(
+    Display::return_message($plugin->get_lang('ErrorOccurred'), 'error', false)
+);
+
+header('Location: '. api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_catalog.php');
+
+exit;

+ 39 - 0
plugin/buycourses/src/service_information.php

@@ -0,0 +1,39 @@
+<?php
+
+/* For licensing terms, see /license.txt */
+/**
+ * Service information page
+ * Show information about a service (for custom purposes)
+ * @author José Loguercio Silva <jose.loguercio@beeznest.com>
+ * @package chamilo.buycourses_plugin
+ */
+
+$cidReset = true;
+
+require_once '../../../main/inc/global.inc.php';
+
+$serviceId = isset($_GET['service_id']) ? intval($_GET['service_id']) : false;
+
+$plugin = BuyCoursesPlugin::create();
+
+$includeServices = $plugin->get('include_services') === 'true';
+
+if (!$includeServices) {
+    api_not_allowed(true);
+}
+
+$service = $plugin->getServices($serviceId);
+
+if (!$service['id']) {
+    api_not_allowed(true);
+}
+
+$template = new Template(false);
+$template->assign('pageUrl', api_get_path(WEB_PATH) . "service/{$serviceId}/information/");
+$template->assign('service', $service);
+$template->assign('essence', Essence\Essence::instance());
+
+$content = $template->fetch('buycourses/view/service_information.tpl');
+
+$template->assign('content', $content);
+$template->display_one_col_template();

+ 234 - 0
plugin/buycourses/src/service_process.php

@@ -0,0 +1,234 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Process payments for the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Initialization
+ */
+
+$cidReset = true;
+
+require_once '../config.php';
+
+if (!isset($_REQUEST['t'], $_REQUEST['i'])) {
+    header('Location: ' . api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_catalog.php');
+}
+
+$currentUserId = api_get_user_id();
+$serviceId = intval($_REQUEST['i']);
+
+if (empty($currentUserId)) {
+    api_not_allowed(true);
+}
+
+$em = Database::getManager();
+$plugin = BuyCoursesPlugin::create();
+$includeServices = $plugin->get('include_services');
+$paypalEnabled = $plugin->get('paypal_enable') === 'true';
+$transferEnabled = $plugin->get('transfer_enable') === 'true';
+$wizard = true;
+$additionalQueryString = '';
+if ($includeServices !== 'true') {
+    api_not_allowed(true);
+}
+
+$typeUser = intval($_REQUEST['t']) === BuyCoursesPlugin::SERVICE_TYPE_USER;
+$typeCourse = intval($_REQUEST['t']) === BuyCoursesPlugin::SERVICE_TYPE_COURSE;
+$typeSession = intval($_REQUEST['t']) === BuyCoursesPlugin::SERVICE_TYPE_SESSION;
+$queryString = 'i=' . intval($_REQUEST['i']) . '&t=' . intval($_REQUEST['t']).$additionalQueryString;
+
+$serviceInfo = $plugin->getServices(intval($_REQUEST['i']));
+$userInfo = api_get_user_info($currentUserId);
+
+$form = new FormValidator('confirm_sale');
+
+if ($form->validate()) {
+    $formValues = $form->getSubmitValues();
+    
+    if (!$formValues['payment_type']) {
+        Display::addFlash(
+            Display::return_message($plugin->get_lang('NeedToSelectPaymentType'), 'error', false)
+        );
+        header('Location:' . api_get_self() . '?' . $queryString);
+        exit;
+    }
+
+    if (!$formValues['info_select']) {
+        Display::addFlash(
+            Display::return_message($plugin->get_lang('AdditionalInfoRequired'), 'error', false)
+        );
+        header('Location:' . api_get_self() . '?' . $queryString);
+        exit;
+    }
+
+    $userGroup = $em->getRepository('ChamiloCoreBundle:Usergroup')->findBy(['name' => $formValues['info_select']]);
+
+    if ($userGroup) {
+        Display::addFlash(
+            Display::return_message($plugin->get_lang('StoreNameAlreadyExist'), 'error', false)
+        );
+        header('Location:' . api_get_self() . '?' . $queryString);
+        exit;
+    }
+    
+    $serviceSaleId = $plugin->registerServiceSale($serviceId, $formValues['payment_type'], $formValues['info_select'], $formValues['enable_trial']);
+
+    if (!empty($formValues['store_code'])) {
+        $data = [
+            'store_code' => Security::remove_XSS($formValues['store_code']),
+            'store_name' => Security::remove_XSS($formValues['info_select']),
+            'parent_id' => 0,
+            'description' => 'Registered by User in buying process',
+            'type' => 1,
+            'discount' => 0
+        ];
+
+        $verification = $plugin->getDiscountByCode($data['store_code']);
+
+        if (!$verification) {
+            $plugin->addDiscountCode($data);
+        }
+    }
+
+    if ($serviceSaleId !== false) {
+        $_SESSION['bc_service_sale_id'] = $serviceSaleId;
+        
+        if ($verification['discount'] == 100) {
+
+            $serviceSale = $plugin->getServiceSale($serviceSaleId);
+
+            $serviceSaleIsCompleted = $plugin->completeServiceSale($serviceSale['id']);
+            if ($serviceSaleIsCompleted) {
+                Display::addFlash(Display::return_message(sprintf($plugin->get_lang('SubscriptionToServiceXSuccessful'), $serviceSale['service']['name']), 'success'));
+
+                $plugin->SendSubscriptionMail(intval($serviceSale['id']));
+
+                unset($_SESSION['bc_service_sale_id']);
+
+                header('Location: ' . api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/package_panel.php?id='.$serviceSale['id']);
+                exit;
+            }
+        }
+        
+        if ($wizard) {
+            header('Location: ' . api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_process_confirm.php?from=register');
+        } else {
+            header('Location: ' . api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/service_process_confirm.php');
+        }
+    }
+
+    exit;
+}
+
+// Reset discount code
+unset($_SESSION['s_discount']);
+
+$paymentTypesOptions = $plugin->getPaymentTypes(true);
+
+$form->addHeader('');
+$form->addRadio('payment_type', null, $paymentTypesOptions);
+$form->addHtml('<h3 class="panel-heading">'.$plugin->get_lang('AdditionalInfo').'</h3>');
+$form->addHeader('');
+$form->addHtml(Display::return_message($plugin->get_lang('PleaseSelectTheCorrectInfoToApplyTheService'), 'info'));
+$selectOptions = [];
+
+if ($typeUser) {
+    $users = $em->getRepository('ChamiloUserBundle:User')->findAll();
+    $selectOptions[$userInfo['user_id']] = api_get_person_name($userInfo['firstname'], $userInfo['lastname']) . ' (' . get_lang('Myself') . ')';
+    if (!empty($users)) {
+        foreach ($users as $user) {
+            if (intval($userInfo['user_id']) !== intval($user->getId())) {
+                $selectOptions[$user->getId()] = $user->getCompleteNameWithUsername();
+            }
+        }
+    }
+    $form->addSelect('info_select', get_lang('User'), $selectOptions);
+} elseif ($typeCourse) {
+    $user = $em->getRepository('ChamiloUserBundle:User')->find($currentUserId);
+    $courses = $user->getCourses();
+    if (!empty($courses)) {
+        foreach ($courses as $course) {
+            $selectOptions[$course->getCourse()->getId()] = $course->getCourse()->getTitle();
+        }
+    }
+    $form->addSelect('info_select', get_lang('Course'), $selectOptions);
+} elseif ($typeSession) {
+    $user = $em->getRepository('ChamiloUserBundle:User')->find($currentUserId);
+    $sessions = $user->getSessionCourseSubscriptions();
+    if (!empty($sessions)) {
+        foreach ($sessions as $session) {
+            $selectOptions[$session->getSession()->getId()] = $session->getSession()->getName();
+        }
+    }
+    $form->addSelect('info_select', get_lang('Session'), $selectOptions);
+} elseif ($typeSubscriptionPackage) {
+    $trial = intval($serviceInfo['allow_trial']);
+
+    if ($trial) {
+        $trialTime = $serviceInfo['trial_period'] == 'Month' ? get_lang($serviceInfo['trial_period']) . '(es)' : get_lang($serviceInfo['trial_period']) . '(s)';
+        $form->addHtml('
+            <div class="form-group ">
+                <label for="qf_373cc5" class="col-sm-6">
+                    ' . sprintf($plugin->get_lang('EnableTrialSubscription'), $serviceInfo['trial_frequency'] . ' ' . $trialTime) . '
+                </label>
+                <div class="col-sm-6">
+                    <input cols-size="" name="enable_trial" value="1" id="qf_373cc5" type="checkbox"></div>
+                <div class="col-sm-0"></div>
+            </div>
+            <div class="form-group ">
+                <div class="col-sm-12">
+                    <p class="help-block">' . sprintf($plugin->get_lang('EnableTrialSubscriptionHelpText'), $serviceInfo['trial_frequency'] . ' ' . $trialTime) . '</p>
+                </div>
+            </div>
+        ');
+    }
+    $form->addText('store_code', $plugin->get_lang('DiscountCodeProcess'), true, ['cols-size' => [6, 6, 0], 'id' => 'store_code']);
+    $form->addText('info_select_trick', $plugin->get_lang('StoreName'), true, ['cols-size' => [6, 6, 0], 'id' => 'info_select_trick']);
+    $form->addHidden('info_select', '');
+    $form->addHtml('
+        <div class="form-group">
+            <div class="col-sm-2 pull-right">
+                <a id="code-checker" class="btn btn-xs btn-warning">' . $plugin->get_lang('Check') . '</a>
+            </div>
+            <div id="code-verificator-text" class="col-sm-4 pull-right">
+            </div>
+        </div>
+        <div id="code-verificator-info">
+
+        </div>
+        <div class="form-group">
+            <div class="col-sm-12">
+                <p class="help-block">' . $plugin->get_lang('DiscountCodeInfoText') . '</p>
+            </div>
+        </div>
+    ');
+}
+
+
+$form->addHidden('t', intval($_GET['t']));
+$form->addHidden('i', intval($_GET['i']));
+
+$form->addButton('submit', $plugin->get_lang('ConfirmOrder'), 'check', 'success');
+
+// View
+$templateName = $plugin->get_lang('PaymentMethods');
+$interbreadcrumb[] = array("url" => "service_catalog.php", "name" => $plugin->get_lang('ListOfServicesOnSale'));
+
+$tpl = new Template($templateName);
+if (isset($_GET['from'])) {
+    if($_GET['from'] == 'register') {
+        $tpl->assign('wizard', true);
+    }
+}
+$tpl->assign('buying_service', true);
+$tpl->assign('service', $serviceInfo);
+$tpl->assign('user', api_get_user_info());
+$tpl->assign('form', $form->returnForm());
+
+
+$content = $tpl->fetch('buycourses/view/process.tpl');
+
+$tpl->assign('content', $content);
+$tpl->display_one_col_template();

+ 95 - 0
plugin/buycourses/src/service_sales_report.php

@@ -0,0 +1,95 @@
+<?php
+
+/* For license terms, see /license.txt */
+/**
+ * List of pending payments of the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+//Initialization
+$cidReset = true;
+
+require_once '../config.php';
+
+api_protect_admin_script();
+
+$plugin = BuyCoursesPlugin::create();
+
+$paypalEnable = $plugin->get('paypal_enable');
+$commissionsEnable = $plugin->get('commissions_enable');
+$includeServices = $plugin->get('include_services');
+
+$saleStatuses = $plugin->getServiceSaleStatuses();
+$paymentTypes = $plugin->getPaymentTypes();
+
+$form = new FormValidator('search', 'get');
+
+$form->addSelect('status', $plugin->get_lang('OrderStatus'), $saleStatuses, ['cols-size' => [0, 0, 0]]);
+$form->addText('user', get_lang('User'), false, ['cols-size' => [0, 0, 0]]);
+$form->addButtonSearch(get_lang('Search'), 'search');
+
+$servicesSales = $plugin->getServiceSale();
+$serviceSaleList = [];
+
+foreach ($servicesSales as $sale) {
+    $serviceSaleList[] = [
+        'id' => $sale['id'],
+        'reference' => $sale['reference'],
+        'status' => $sale['status'],
+        'date' => api_format_date($sale['buy_date'], DATE_TIME_FORMAT_LONG_24H),
+        'currency' => $sale['currency'],
+        'price' => $sale['price'],
+        'service_type' => $sale['service']['applies_to'],
+        'service_name' => $sale['service']['name'],
+        'complete_user_name' => $sale['buyer']['name']
+    ];
+}
+
+//View
+$interbreadcrumb[] = ['url' => '../index.php', 'name' => $plugin->get_lang('plugin_title')];
+
+$templateName = $plugin->get_lang('SalesReport');
+
+$template = new Template($templateName);
+
+$toolbar = '';
+
+if ($paypalEnable == 'true' && $commissionsEnable == 'true') {
+
+    $toolbar .= Display::toolbarButton(
+        $plugin->get_lang('PaypalPayoutCommissions'),
+        api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/paypal_payout.php',
+        'paypal',
+        'primary',
+        ['title' => $plugin->get_lang('PaypalPayoutCommissions')]
+    );
+    
+    $template->assign('actions', $toolbar);
+    
+}
+
+if ($commissionsEnable == 'true') {
+
+    $toolbar .= Display::toolbarButton(
+        $plugin->get_lang('PayoutReport'),
+        api_get_path(WEB_PLUGIN_PATH) . 'buycourses/src/payout_report.php',
+        'money',
+        'info',
+        ['title' => $plugin->get_lang('PayoutReport')]
+    );
+    
+    $template->assign('actions', $toolbar);
+    
+}
+$template->assign('form', $form->returnForm());
+$template->assign('showing_services', true);
+$template->assign('services_are_included', $includeServices);
+$template->assign('sale_list', $serviceSaleList);
+$template->assign('sale_status_cancelled', BuyCoursesPlugin::SERVICE_STATUS_CANCELLED);
+$template->assign('sale_status_pending', BuyCoursesPlugin::SERVICE_STATUS_PENDING);
+$template->assign('sale_status_completed', BuyCoursesPlugin::SERVICE_STATUS_COMPLETED);
+
+$content = $template->fetch('buycourses/view/service_sales_report.tpl');
+
+
+$template->assign('content', $content);
+$template->display_one_col_template();

+ 123 - 0
plugin/buycourses/src/services_add.php

@@ -0,0 +1,123 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Create new Services for the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Init
+ */
+
+$cidReset = true;
+
+require_once '../../../main/inc/global.inc.php';
+
+$plugin = BuyCoursesPlugin::create();
+$currency = $plugin->getSelectedCurrency();
+$em = Database::getManager();
+$users = $em->getRepository('ChamiloUserBundle:User')->findAll();
+$userOptions = [];
+if (!empty($users)) {
+    foreach ($users as $user) {
+        $userOptions[$user->getId()] = $user->getCompleteNameWithUsername();
+    }
+}
+
+api_protect_admin_script(true);
+
+$htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
+$htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
+
+//view
+$interbreadcrumb[] = [
+    'url' => 'configuration.php',
+    'name' => $plugin->get_lang('Configuration')
+];
+
+$formDefaultValues = [
+    'price' => 0,
+    'duration_days' => 0,
+    'applies_to' => 0,
+    'visibility' => true
+];
+
+$form = new FormValidator('Services');
+$form->addText('name', $plugin->get_lang('ServiceName'));
+$form->addTextarea('description', $plugin->get_lang('Description'));
+$form->addElement(
+    'number',
+    'price',
+    [$plugin->get_lang('Price'), null, $currency['iso_code']],
+    ['step' => 0.01]
+);
+$form->addElement(
+    'number',
+    'duration_days',
+    [$plugin->get_lang('Duration'), null, get_lang('Days')],
+    ['step' => 1]
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    $plugin->get_lang('AppliesTo'),
+    get_lang('None'),
+    0
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('User'),
+    1
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Course'),
+    2
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Session'),
+    3
+);
+$form->addSelect(
+    'owner_id',
+    get_lang('Owner'),
+    $userOptions
+);
+$form->addCheckBox('visibility', $plugin->get_lang('VisibleInCatalog'));
+$form->addFile(
+    'picture',
+    (get_lang(
+        'AddImage'
+    )),
+    array('id' => 'picture', 'class' => 'picture-form', 'crop_image' => true, 'crop_ratio' => '16 / 9')
+);
+$form->addText('video_url', get_lang('VideoUrl'), false);
+$form->addHtmlEditor('service_information', $plugin->get_lang('ServiceInformation'), false);
+$form->addButtonSave(get_lang('Add'));
+$form->setDefaults($formDefaultValues);
+
+if ($form->validate()) {
+    $values = $form->getSubmitValues();
+    
+    $plugin->storeService($values);
+
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('ServiceAdded'), 'success')
+    );
+    
+    header('Location: configuration.php');
+    exit;
+}
+
+$templateName = $plugin->get_lang('NewService');
+$tpl = new Template($templateName);
+
+$tpl->assign('header', $templateName);
+$tpl->assign('content', $form->returnForm());
+$tpl->display_one_col_template();

+ 140 - 0
plugin/buycourses/src/services_edit.php

@@ -0,0 +1,140 @@
+<?php
+/* For license terms, see /license.txt */
+/**
+ * Create new Services for the Buy Courses plugin
+ * @package chamilo.plugin.buycourses
+ */
+/**
+ * Init
+ */
+
+$cidReset = true;
+
+require_once '../../../main/inc/global.inc.php';
+
+$serviceId = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : null;
+
+if (!$serviceId) {
+    header('Location: configuration.php');
+}
+
+$plugin = BuyCoursesPlugin::create();
+$currency = $plugin->getSelectedCurrency();
+$em = Database::getManager();
+$users = $em->getRepository('ChamiloUserBundle:User')->findAll();
+$userOptions = [];
+if (!empty($users)) {
+    foreach ($users as $user) {
+        $userOptions[$user->getId()] = $user->getCompleteNameWithUsername();
+    }
+}
+
+api_protect_admin_script(true);
+$htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
+$htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
+
+//view
+$interbreadcrumb[] = [
+    'url' => 'configuration.php',
+    'name' => $plugin->get_lang('Configuration')
+];
+
+$service = $plugin->getServices($serviceId);
+
+$formDefaultValues = [
+    'name' => $service['name'],
+    'description' => $service['description'],
+    'price' => $service['price'],
+    'duration_days' => $service['duration_days'],
+    'owner_id' => intval($service['owner_id']),
+    'applies_to' => intval($service['applies_to']),
+    'visibility' => ($service['visibility'] == 1) ? true : false,
+    'image' =>
+    is_file(api_get_path(SYS_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$serviceId.'.png')
+        ?
+    api_get_path(WEB_PLUGIN_PATH).'buycourses/uploads/services/images/simg-'.$serviceId.'.png'
+        :
+    api_get_path(WEB_CODE_PATH).'img/session_default.png',
+    'video_url' => $service['video_url'],
+    'service_information' => $service['service_information']
+];
+
+$form = new FormValidator('Services');
+$form->addText('name', $plugin->get_lang('ServiceName'));
+$form->addTextarea('description', $plugin->get_lang('Description'));
+$form->addElement(
+    'number',
+    'price',
+    [$plugin->get_lang('Price'), null, $currency['iso_code']],
+    ['step' => 0.01]
+);
+$form->addElement(
+    'number',
+    'duration_days',
+    [$plugin->get_lang('Duration'), null, get_lang('Days')],
+    ['step' => 1]
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    $plugin->get_lang('AppliesTo'),
+    get_lang('None'),
+    0
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('User'),
+    1
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Course'),
+    2
+);
+$form->addElement(
+    'radio',
+    'applies_to',
+    null,
+    get_lang('Session'),
+    3
+);
+$form->addSelect(
+    'owner_id',
+    get_lang('Owner'),
+    $userOptions
+);
+$form->addCheckBox('visibility', $plugin->get_lang('VisibleInCatalog'));
+$form->addFile(
+    'picture',
+    ($formDefaultValues['image'] != '' ? get_lang('UpdateImage') : get_lang(
+        'AddImage'
+    )),
+    array('id' => 'picture', 'class' => 'picture-form', 'crop_image' => true, 'crop_ratio' => '16 / 9')
+);
+$form->addText('video_url', get_lang('VideoUrl'), false);
+$form->addHtmlEditor('service_information', $plugin->get_lang('ServiceInformation'), false);
+$form->addHidden('id', $serviceId);
+$form->addButtonSave(get_lang('Edit'));
+$form->setDefaults($formDefaultValues);
+if ($form->validate()) {
+    $values = $form->getSubmitValues();
+    $plugin->updateService($values, $serviceId);
+
+    Display::addFlash(
+        Display::return_message($plugin->get_lang('ServiceEdited'), 'success')
+    );
+
+    header('Location: configuration.php');
+    exit;
+}
+
+$templateName = $plugin->get_lang('EditService');
+$tpl = new Template($templateName);
+
+$tpl->assign('header', $templateName);
+$tpl->assign('content', $form->returnForm());
+$tpl->display_one_col_template();

+ 2 - 0
plugin/buycourses/src/session_catalog.php

@@ -12,6 +12,7 @@ require_once __DIR__.'/../../../main/inc/global.inc.php';
 
 $plugin = BuyCoursesPlugin::create();
 $includeSessions = $plugin->get('include_sessions') === 'true';
+$includeServices = $plugin->get('include_services') === 'true';
 
 if (!$includeSessions) {
     api_not_allowed(true);
@@ -66,6 +67,7 @@ $templateName = $plugin->get_lang('CourseListOnSale');
 $template = new Template($templateName);
 $template->assign('search_filter_form', $form->returnForm());
 $template->assign('sessions_are_included', $includeSessions);
+$template->assign('services_are_included', $includeServices);
 $template->assign('showing_sessions', true);
 $template->assign('sessions', $sessionList);
 

+ 3 - 0
plugin/buycourses/uploads/services/images/.gitignore

@@ -0,0 +1,3 @@
+*
+
+!.gitignore

+ 52 - 0
plugin/buycourses/view/catalog.tpl

@@ -9,6 +9,11 @@
             <li id="buy-sessions-tab" class="{{ showing_sessions ? 'active' : '' }}" role="presentation">
                 <a href="session_catalog.php" aria-controls="buy-sessions" role="tab">{{ 'Sessions'|get_lang }}</a>
             </li>
+            {% if services_are_included %}
+                <li id="buy-services-tab" class="{{ showing_services ? 'active' : '' }}" role="presentation">
+                    <a href="service_catalog.php" aria-controls="buy-services" role="tab">{{ 'Services'|get_plugin_lang('BuyCoursesPlugin') }}</a>
+                </li>
+            {% endif %}
         </ul>
     {% endif %}
 
@@ -104,6 +109,53 @@
                                 </div>
                             {% endfor %}
                         {% endif %}
+
+                        {% if showing_services %}
+                            {% for service in services %}
+                                <div class="col-md-4 col-sm-6">
+                                    <div class="items-course">
+                                        <div class="items-course-image">
+                                            <a href="{{ _p.web }}service/{{ service.id }}"><img alt="{{ service.name }}" class="img-responsive" src="{{ service.image }}"></a>
+                                        </div>
+                                        <div class="items-course-info">
+                                            <h4 class="title">
+                                                <a title="{{ service.name }}" href="{{ _p.web }}service/{{ service.id }}" >
+                                                    {{ service.name }}
+                                                </a>
+                                            </h4>
+                                            <ul class="list-unstyled">
+                                                <li><em class="fa fa-clock-o"></em> {{ 'Duration'|get_plugin_lang('BuyCoursesPlugin') }} : {{ service.duration_days == 0 ? 'NoLimit' | get_lang  : service.duration_days ~ ' ' ~ 'Days' | get_lang }}</li>
+                                                {% if service.applies_to == 0 %}
+                                                    <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'None' | get_lang }}</li>
+                                                {% elseif service.applies_to == 1 %}
+                                                    <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'User' | get_lang }}</li>
+                                                {% elseif service.applies_to == 2 %}
+                                                    <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'Course' | get_lang }}</li>
+                                                {% elseif service.applies_to == 3 %}
+                                                    <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'Session' | get_lang }}</li>
+                                                {% elseif service.applies_to == 4 %}
+                                                    <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'SubscriptionPackage' | get_plugin_lang('BuyCoursesPlugin') }}</li>
+                                                {% endif %}
+                                                <li><em class="fa fa-user"></em> {{ service.owner_name }}</li>
+                                            </ul>
+                                            <p class="text-right">
+                                                <span class="label label-primary">
+                                                    {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}
+                                                </span>
+                                            </p>
+                                            <div class="toolbar">
+                                                <a class="btn btn-info btn-block btn-sm" title="" href="{{ _p.web }}service/{{ service.id }}">
+                                                    <em class="fa fa-info-circle"></em> {{ 'ServiceInformation'|get_plugin_lang('BuyCoursesPlugin') }}
+                                                </a>
+                                                <a class="btn btn-success btn-block btn-sm" title="" href="{{ _p.web_plugin ~ 'buycourses/src/service_process.php?' ~ {'i': service.id, 't': service.applies_to}|url_encode() }}">
+                                                    <em class="fa fa-shopping-cart"></em> {{ 'Buy'|get_plugin_lang('BuyCoursesPlugin') }}
+                                                </a>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            {% endfor %}
+                        {% endif %}
                     </div>
                 </div>
             </div>

+ 66 - 2
plugin/buycourses/view/configuration.tpl

@@ -1,5 +1,3 @@
-<script type='text/javascript' src="../js/buycourses.js"></script>
-
 <link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
 
 {% if sessions_are_included %}
@@ -10,6 +8,11 @@
         <li role="presentation">
             <a href="#sessions" aria-controls="sessions" role="tab" data-toggle="tab">{{ 'Sessions'|get_lang }}</a>
         </li>
+        {% if services_are_included %}
+            <li role="presentation">
+                <a href="#services" aria-controls="services" role="tab" data-toggle="tab">{{ 'Services'|get_plugin_lang('BuyCoursesPlugin') }}</a>
+            </li>
+        {% endif %}
     </ul>
 {% endif %}
 
@@ -131,4 +134,65 @@
             </div>
         </div>
     {% endif %}
+    {% if services_are_included %}
+        <div role="tabpanel" class="tab-pane" id="services">
+            <div class="table-responsive">
+                <a href="{{ _p.web_plugin ~ 'buycourses/src/services_add.php' }}" class="btn btn-primary">
+                    <em class="fa fa-cart-plus fa-fw"></em> {{ 'NewService'| get_plugin_lang('BuyCoursesPlugin') }}
+                </a>
+                </br>
+                </br>
+                <table id="services_table" class="table">
+                    <thead>
+                    <tr>
+                        <th>{{ 'Service'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th>{{ 'Description'|get_lang }}</th>
+                        <th class="text-center">{{ 'Duration'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th class="text-center">{{ 'VisibleInCatalog'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th class="text-center">{{ 'Owner'|get_lang }}</th>
+                        <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                        <th class="text-right">{{ 'Options'|get_lang }}</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    {% for item in services %}
+                        <tr data-item="{{ item.id }}" data-type="service">
+                            <td>
+                                {{ item.name }}
+                            </td>
+                            <td>
+                                {{ item.description }}
+                            </td>
+                            <td class="text-center">
+                                {% if item.duration_days == 0 %}
+                                    {{ 'NoLimit'|get_lang }}
+                                {% else %}
+                                    {{ item.duration_days }} {{ 'Days'|get_lang }}
+                                {% endif %}
+                            </td>
+                            <td class="text-center">
+                                {% if item.visibility == 1 %}
+                                    <em class="fa fa-fw fa-check-square-o"></em>
+                                {% else %}
+                                    <em class="fa fa-fw fa-square-o"></em>
+                                {% endif %}
+                            </td>
+                            <td class="text-center">
+                                {{ item.owner_name }}
+                            </td>
+                            <td class="text-right" width="200">
+                                {{ "#{item.price} #{tem.currency ?: item.currency}" }}
+                            </td>
+                            <td class="text-right">
+                                <a href="{{ _p.web_plugin ~ 'buycourses/src/services_edit.php?' ~ {'id': item.id}|url_encode() }}" class="btn btn-info btn-sm">
+                                    <em class="fa fa-wrench fa-fw"></em> {{ 'Edit'|get_lang }}
+                                </a>
+                            </td>
+                        </tr>
+                    {% endfor %}
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    {% endif %}
 </div>

+ 91 - 3
plugin/buycourses/view/process.tpl

@@ -1,8 +1,9 @@
 <script type='text/javascript' src="../js/buycourses.js"></script>
 
 <div class="row">
-    <div class="col-md-7">
-        <h3 class="page-header">{{ 'PurchaseData'|get_plugin_lang('BuyCoursesPlugin') }}</h3>
+    <div class="col-md-5 panel panel-default buycourse-panel-default">
+        <h3 class="panel-heading">{{ 'PurchaseData'|get_plugin_lang('BuyCoursesPlugin') }}</h3>
+        <legend></legend>
         <div class="row">
             {% if buying_course %}
                 <div class="col-sm-6 col-md-5">
@@ -43,10 +44,97 @@
                         {% endfor %}
                     </dl>
                 </div>
+            {% elseif buying_service %}
+                <div class="col-sm-12 col-md-12 col-xs-12">
+                    <a href='{{ _p.web }}service/{{ service.id }}'>
+                        <img alt="{{ service.name }}" class="img-responsive" src="{{ _p.web }}plugin/buycourses/uploads/services/images/{{ service.image }}">
+                    </a>
+                </div>
+                <div class="col-sm-12 col-md-12 col-xs-12">
+                    <h3>
+                        <a href='{{ _p.web }}service/{{ service.id }}'>{{ service.name }}</a>
+                    </h3>
+                    <ul class="list-unstyled">
+                        {% if service.applies_to == 0 %}
+                            <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'None' | get_lang }}</li>
+                        {% elseif service.applies_to == 1 %}
+                            <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'User' | get_lang }}</li>
+                        {% elseif service.applies_to == 2 %}
+                            <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'Course' | get_lang }}</li>
+                        {% elseif service.applies_to == 3 %}
+                            <li><em class="fa fa-hand-o-right"></em> {{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }} {{ 'Session' | get_lang }}</li>
+                        {% endif %}
+                        <li><em class="fa fa-money"></em> {{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }} : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }} / {{ service.duration_days == 0 ? 'NoLimit' | get_lang  : service.duration_days ~ ' ' ~ 'Days' | get_lang }} </li>
+                        <li><em class="fa fa-user"></em> {{ service.owner_name }}</li>
+                        <li><em class="fa fa-align-justify"></em> {{ service.description }}</li>
+                    </ul>
+                    <p id="n-price" class="lead text-right" style="color: white;"><span class="label label-primary">{{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}</span></p>
+                    <p id="s-price" class="lead text-right"></p>
+                </div>
+                <script>
+                    $(document).ready(function() {
+                        $("input[name=info_select_trick]").prop('disabled', true);
+                        $("input[name=payment_type]").attr('hidden', true);
+                        $("input[name=payment_type]").attr('checked', true);
+                        $("#paypal-icon").html(' <em class="fa fa-check text-success fa-2x" aria-hidden="true"></em>');
+                        $("input[name=payment_type]").click(function () {
+                            $("#paypal-icon").html(' <em class="fa fa-check text-success fa-2x" aria-hidden="true"></em>');
+                        })
+                        $("label").removeClass('control-label');
+                        $('.form_required').remove();
+                        $("small").remove();
+                        $("label[for=submit]").remove();
+                        $("input[name=enable_trial]").attr('checked', true);
+
+                        $("#code-checker").click(function () {
+                            var code = $("#store_code").val();
+                            $.ajax({
+                                beforeSend: function() {
+                                    $("#code-checker").html('<em class="fa fa-refresh fa-spin"></em> {{ 'Loading' | get_lang }}');
+                                },
+                                type: "POST",
+                                url: "{{ _p.web_plugin ~ 'buycourses/src/buycourses.ajax.php?a=verify_discount_code' }}",
+                                data : { store_code: code},
+                                success: function(response) {
+                                    $("#code-checker").html('{{ 'Check' | get_plugin_lang('BuyCoursesPlugin') }}');
+                                    if (response.msg == 'true') {
+                                        var store_name = response.store_name;
+                                        var description = response.description;
+                                        var type = response.type;
+                                        var parent = '';
+                                        if (response.parent) {
+                                            parent = '<div class="form-group"><label for="code_parent" class="col-sm-6">{{ 'Parent' | get_plugin_lang('BuyCoursesPlugin') }}</label><div class="col-sm-6">' + response.parent + '</div></div>'
+                                        }
+                                        $("input[name=info_select]").val(store_name);
+                                        $("#info_select_trick").val(store_name);
+                                        $("#code-verificator-text").html('<p style="color: green">{{ 'ValidCode' | get_plugin_lang('BuyCoursesPlugin') }}</p>');
+                                        $("#code-verificator-info").html('' +
+                                            '<div class="form-group"><label for="code_description" class="col-sm-6">{{ 'Description' | get_plugin_lang('BuyCoursesPlugin') }}</label><div class="col-sm-6">' + description + '</div></div>' +
+                                            '<div class="form-group"><label for="code_type" class="col-sm-6">{{ 'Type' | get_plugin_lang('BuyCoursesPlugin') }}</label><div class="col-sm-6">' + type + '</div></div>' + parent +
+                                            '');
+                                        var price = {{ service.price }};
+                                        var show = response.discount;
+                                        var discount = price * (response.discount / 100);
+                                        var total = price - discount;
+                                        $("#n-price").css('text-decoration', 'line-through');
+                                        $("#s-price").html('<b>Desconto especial de ' + show + '%</b> <span class="label label-success">{{ service.currency == 'BRL' ? 'R$' : service.currency }} ' + total.toFixed(2) + '</span>');
+                                    } else if (response.msg == 'false') {
+                                        $("#code-verificator-text").html('<p style="color: red">{{ 'CodeDoesntExist' | get_plugin_lang('BuyCoursesPlugin') }}</p>');
+                                        $("input[name=info_select]").val($("#store_code").val());
+                                        $("#info_select_trick").val($("#store_code").val());
+                                    }
+                                }
+                            });
+                        });
+                    });
+                </script>
             {% endif %}
         </div>
     </div>
-    <div class="col-md-5">
+    <div class="col-md-1">
+    </div>
+    <div class="col-md-6 panel panel-default buycourse-panel-default">
+        <h3 class="panel-heading">{{ 'PaymentMethods' | get_plugin_lang('BuyCoursesPlugin') }}</h3>
         {{ form }}
     </div>
 </div>

+ 13 - 0
plugin/buycourses/view/sales_report.tpl

@@ -1,3 +1,16 @@
+<link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
+<ul class="nav nav-tabs buy-courses-sessions-tabs" role="tablist">
+    <li id="buy-courses-sessions-tab" class="active" role="presentation">
+        <a href="sales_report.php" aria-controls="buy-courses_sessions" role="tab">{{ 'CourseSessionBlock'|get_lang }}</a>
+    </li>
+    {% if services_are_included %}
+        <li id="buy-services-tab" class="{{ showing_services ? 'active' : '' }}" role="presentation">
+            <a href="service_sales_report.php" aria-controls="buy-services" role="tab">{{ 'Services'|get_plugin_lang('BuyCoursesPlugin') }}</a>
+        </li>
+    {% endif %}
+</ul>
+</br>
+</br>
 {{ form }}
 
 <div class="table-responsive">

+ 80 - 0
plugin/buycourses/view/service_information.tpl

@@ -0,0 +1,80 @@
+<link rel="stylesheet" type="text/css" href="{{ _p.web_plugin ~ 'buycourses/resources/css/style.css' }}"/>
+
+<div id="service-information">
+
+    <div class="row">
+        <div class="col-xs-12">
+            <h3 class="text-uppercase buy-courses-title-color">{{ service.name }}</h3>
+        </div>
+
+        {% if service.video_url %}
+            <div class="col-sm-6 col-md-7 col-xs-12">
+                <div class="embed-responsive embed-responsive-16by9">
+                    {{ essence.replace(service.video_url) }}
+                </div>
+            </div>
+        {% endif %}
+
+        <div class="{{ service.video_url ? 'col-sm-6 col-md-5 col-xs-12' : 'col-sm-12 col-xs-12' }}">
+            <div class="block">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h4>{{ "Description"|get_lang }}</h4>
+                    </div>
+                    <div class="panel-body">
+                        <p><em class="fa fa-flag-o"></em> <b>{{ 'AppliesTo'|get_plugin_lang('BuyCoursesPlugin') }}</b> :
+                            {% if service.applies_to == 1 %}
+                                {{ 'User' | get_lang }}
+                            {% elseif service.applies_to == 2 %}
+                                {{ 'Course' | get_lang }}
+                            {% elseif service.applies_to == 3 %}
+                                {{ 'Session' | get_lang }}
+                            {% endif %}
+                        </p>
+                        <p><em class="fa fa-money"></em> <b>{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</b> : {{ service.currency == 'BRL' ? 'R$' : service.currency }} {{ service.price }}</p>
+                        <p><em class="fa fa-align-justify"></em> <b>{{ 'Details' | get_lang }}</b> : {{ service.description }}</p>
+                        <div class="text-right" style="padding-bottom: 20px;">
+                            <a href="{{ _p.web_plugin ~ 'buycourses/src/service_process.php?t=4&i=' ~ service.id }}" class="btn btn-success btn-lg">
+                                <em class="fa fa-shopping-cart"></em> {{ 'Buy'|get_plugin_lang('BuyCoursesPlugin') }}
+                            </a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    </br>
+    </br>
+    <div class="row info-course">
+        <div class="col-xs-12 col-md-7">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h4>{{ 'ServiceInformation'|get_plugin_lang('BuyCoursesPlugin') }}</h4>
+                </div>
+                <div class="panel-body">
+                    {{ service.service_information }}
+                </div>
+            </div>
+        </div>
+        <div class="col-xs-12 col-md-5">
+            <div class="panel panel-default social-share">
+                <div class="panel-heading">
+                    <h4>{{ "ShareWithYourFriends"|get_lang }}</h4>
+                </div>
+                <div class="panel-body">
+                    <div class="icons-social text-center">
+                        <a href="https://www.facebook.com/sharer/sharer.php?{{ {'u': pageUrl}|url_encode }}" target="_blank" class="btn bnt-link btn-lg">
+                            <em class="fa fa-facebook fa-2x"></em>
+                        </a>
+                        <a href="https://twitter.com/home?{{ {'status': session.getName() ~ ' ' ~ pageUrl}|url_encode }}" target="_blank" class="btn bnt-link btn-lg">
+                            <em class="fa fa-twitter fa-2x"></em>
+                        </a>
+                        <a href="https://www.linkedin.com/shareArticle?{{ {'mini': 'true', 'url': pageUrl, 'title': session.getName() }|url_encode }}" target="_blank" class="btn bnt-link btn-lg">
+                            <em class="fa fa-linkedin fa-2x"></em>
+                        </a>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 98 - 0
plugin/buycourses/view/service_sales_report.tpl

@@ -0,0 +1,98 @@
+<link rel="stylesheet" type="text/css" href="../resources/css/style.css"/>
+<script type="text/javascript" src="../resources/js/modals.js"></script>
+
+<ul class="nav nav-tabs buy-courses-sessions-tabs" role="tablist">
+    <li id="buy-courses-sessions-tab" class="" role="presentation">
+        <a href="sales_report.php" aria-controls="buy-courses_sessions" role="tab">{{ 'CourseSessionBlock'|get_lang }}</a>
+    </li>
+    <li id="buy-services-tab" class="active" role="presentation">
+        <a href="service_sales_report.php" aria-controls="buy-services" role="tab">{{ 'Services'|get_plugin_lang('BuyCoursesPlugin') }}</a>
+    </li>
+</ul>
+</br>
+</br>
+<div class="row">
+    <div class="col-md-3 col-sm-12 col-xs-12">
+        <h4><b>{{ 'Filter' | get_lang }}</b></h4>
+        {{ form }}
+    </div>
+    <div class="col-md-9 col-sm-12 col-xs-12">
+        <div class="table-responsive">
+            <table class="table table-striped table-hover">
+                <thead>
+                <tr>
+                    <th class="text-center">{{ 'ServiceName'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'OrderReference'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'OrderStatus'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'OrderDate'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-right">{{ 'Price'|get_plugin_lang('BuyCoursesPlugin') }}</th>
+                    <th class="text-center">{{ 'ServiceSaleInfo'|get_plugin_lang('BuyCoursesPlugin')  }}</th>
+                </tr>
+                </thead>
+                <tbody>
+                {% for sale in sale_list %}
+                    <tr>
+                        <td class="text-center">{{ sale.service_name }}</td>
+                        <td class="text-center">{{ sale.reference }}</td>
+                        <td class="text-center">
+                            {% if sale.status == sale_status_cancelled %}
+                                {{ 'SaleStatusCancelled'|get_plugin_lang('BuyCoursesPlugin') }}
+                            {% elseif sale.status == sale_status_pending %}
+                                {{ 'SaleStatusPending'|get_plugin_lang('BuyCoursesPlugin') }}
+                            {% elseif sale.status == sale_status_completed %}
+                                {{ 'SaleStatusCompleted'|get_plugin_lang('BuyCoursesPlugin') }}
+                            {% endif %}
+                        </td>
+                        <td class="text-center">{{ sale.date }}</td>
+                        <td class="text-right">{{ sale.currency ~ ' ' ~ sale.price }}</td>
+                        <td class="text-center">
+                            <a id="service_sale_info" tag="{{ sale.id }}" name="s_{{ sale.id }}" class="btn btn-info btn-sm">{{ 'Info' | get_lang }}</a>
+                        </td>
+                    </tr>
+                {% endfor %}
+                </tbody>
+            </table>
+        </div>
+    </div>
+</div>
+
+<script>
+    $(document).on('ready', function () {
+        $("td a").click(function() {
+            var id = $(this).attr('tag');
+            var action = $(this).attr('id');
+            $.ajax({
+                data: 'id='+id,
+                url: '{{ _p.web_plugin }}buycourses/src/buycourses.ajax.php?a='+action,
+                type: 'POST',
+                beforeSend: function() {
+                    if (action == 'renewable_info') {
+                        $('a[name=r_'+id+']').html('<em class="fa fa-spinner fa-pulse"></em> {{ 'Loading' | get_lang }}');
+                    } else if (action == 'service_sale_info') {
+                        $('a[name=s_'+id+']').html('<em class="fa fa-spinner fa-pulse"></em> {{ 'Loading' | get_lang }}');
+                    }
+                },
+                success: function(response) {
+                    $('a[name=r_'+id+']').html('{{ 'Info' | get_lang }}');
+                    $('a[name=s_'+id+']').html('{{ 'Info' | get_lang }}');
+                    var title = "";
+                    if (action == "renewable_info") {
+                        title = "{{ 'RecurringPaymentProfilePaypalInformation' | get_plugin_lang('BuyCoursesPlugin') }}";
+                    } else if (action == 'service_sale_info') {
+                        title = "{{ 'ServiceSaleInfo' | get_plugin_lang('BuyCoursesPlugin') }}";
+                    }
+                    bootbox.dialog({
+                        message: response,
+                        title: title,
+                        buttons: {
+                            main: {
+                                label: "{{ 'Close' | get_lang }}",
+                                className: "btn-default"
+                            }
+                        }
+                    });
+                }
+            })
+        });
+    });
+</script>

+ 1 - 0
plugin/courseblock/CourseBlockPlugin.php

@@ -7,6 +7,7 @@
 class CourseBlockPlugin extends Plugin
 {
     public $isCoursePlugin = true;
+    public $addCourseTool = false;
 
     // When creating a new course this settings are added to the course
     public $course_settings = array(

+ 32 - 11
plugin/google_maps/view/map_coordinates.tpl

@@ -6,11 +6,41 @@
         initMap();
     });
 
+    function addMaker(lat, lng, map, bounds, userInfo) {
+
+        var location = new google.maps.LatLng(lat, lng);
+
+        var infoWindow = new google.maps.InfoWindow();
+
+        var geocoder = geocoder = new google.maps.Geocoder();
+
+        var marker = new google.maps.Marker({
+            map: map,
+            position: location,
+            label: userInfo.complete_name
+        });
+        var address = "";
+
+        geocoder.geocode({ 'latLng': location }, function (results) {
+
+            address = results[1].formatted_address;
+
+            var infoWinContent = "<b>" + userInfo.complete_name + "</b> - " + address;
+
+            marker.addListener('click', function() {
+                infoWindow.setContent(infoWinContent);
+                infoWindow.open(map, marker);
+            });
+        });
+
+        bounds.extend(marker.position);
+        map.fitBounds(bounds);
+    }
+
     function initMap() {
         var center = new google.maps.LatLng(-3.480523, 7.866211);
 
         var bounds = new google.maps.LatLngBounds();
-        var infoWindow = new google.maps.InfoWindow();
 
         var map = new google.maps.Map(document.getElementById("map"), {
             zoom: 2,
@@ -31,18 +61,9 @@
             var lat = latLng[0];
             var lng = latLng[1];
 
-            var location = new google.maps.LatLng(lat, lng);
-
             {% set userInfo = field.itemId | user_info %}
 
-            var marker = new google.maps.Marker({
-                map: map,
-                position: location,
-                label: "{{ userInfo.complete_name }}"
-            });
-
-            bounds.extend(marker.position);
-            map.fitBounds(bounds);
+            addMaker(lat, lng, map, bounds, {{ userInfo|json_encode }});
 
         {% endfor %}
 

+ 1 - 1
plugin/vchamilo/lib/Virtual.php

@@ -29,7 +29,7 @@ class Virtual
         self::getHostName($_configuration);
 
         // We are on physical chamilo. Let original config play
-        $virtualChamiloWebRoot = $_configuration['vchamilo_web_root'].'/';
+        $virtualChamiloWebRoot = rtrim($_configuration['vchamilo_web_root'], '/') . '/';
 
         $virtualChamilo = [];
         if ($_configuration['root_web'] == $virtualChamiloWebRoot) {

+ 1 - 1
plugin/vchamilo/views/manage.php

@@ -14,7 +14,7 @@ define('CHAMILO_INTERNAL', true);
 $plugin = VChamiloPlugin::create();
 $thisurl = api_get_path(WEB_PLUGIN_PATH).'vchamilo/views/manage.php';
 
-Virtual::requireJs('host_list.js', 'vchamilo');
+Virtual::requireJs('host_list.js', 'vchamilo', 'head');
 
 if ($action) {
     require_once api_get_path(SYS_PLUGIN_PATH).'vchamilo/views/manage.controller.php';

+ 25 - 6
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/AgendaStudent.php

@@ -37,7 +37,7 @@ class AgendaStudent extends Basic
     {
         return [
             ['Save', 'NewPage', 'Templates', '-', 'Preview', 'Print'],
-            ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord'],
+            ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', 'inserthtml'],
             ['Undo', 'Redo', '-', 'SelectAll', 'Find', '-', 'RemoveFormat'],
             ['Link', 'Unlink', 'Anchor', 'Glossary'],
             [
@@ -52,13 +52,32 @@ class AgendaStudent extends Basic
                 'Smiley',
                 'SpecialChar',
                 'Asciimath',
-                'Asciisvg'
+                'Asciisvg',
             ],
             '/',
             ['Table', '-', 'CreateDiv'],
-            ['BulletedList', 'NumberedList', 'HorizontalRule', '-', 'Outdent', 'Indent', 'Blockquote'],
+            [
+                'BulletedList',
+                'NumberedList',
+                'HorizontalRule',
+                '-',
+                'Outdent',
+                'Indent',
+                'Blockquote',
+            ],
             ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
-            ['Bold', 'Italic', 'Underline', 'Strike', '-', 'Subscript', 'Superscript', '-', 'TextColor', 'BGColor'],
+            [
+                'Bold',
+                'Italic',
+                'Underline',
+                'Strike',
+                '-',
+                'Subscript',
+                'Superscript',
+                '-',
+                'TextColor',
+                'BGColor',
+            ],
             [api_get_setting('allow_spellcheck') == 'true' ? 'Scayt' : ''],
             ['Styles', 'Format', 'Font', 'FontSize'],
             ['PageBreak', 'ShowBlocks'],
@@ -74,7 +93,7 @@ class AgendaStudent extends Basic
     {
         return [
             ['Save', 'Maximize', 'PasteFromWord', '-', 'Undo', 'Redo'],
-            ['Link', 'Unlink', 'Anchor'],
+            ['Link', 'Unlink', 'Anchor', 'inserthtml'],
             ['Image', 'Video', 'Flash', 'Oembed', 'Youtube', 'Audio'],
             ['Table', 'SpecialChar'],
             '/',
@@ -105,7 +124,7 @@ class AgendaStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord', 'inserthtml'],
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Youtube', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Announcements.php

@@ -33,7 +33,7 @@ class Announcements extends Basic
     {
         return [
             ['Save', 'Maximize', 'PasteFromWord', '-', 'Undo', 'Redo'],
-            ['Link', 'Unlink', 'Anchor'],
+            ['Link', 'Unlink', 'Anchor', 'inserthtml'],
             ['Image', 'Video', 'Flash', 'Oembed', 'Youtube', 'Audio'],
             ['Table', 'SpecialChar'],
             [
@@ -65,7 +65,7 @@ class Announcements extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/AssessmentsIntroduction.php

@@ -52,7 +52,7 @@ class AssessmentsIntroduction extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 10 - 3
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Basic.php

@@ -203,7 +203,7 @@ class Basic extends Toolbar
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Oembed','Flash', 'Youtube', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],
@@ -221,8 +221,8 @@ class Basic extends Toolbar
     protected function getMaximizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'Preview', 'Print'],
-            ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord'],
+            $this->getNewPageBlock(),
+            ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', 'inserthtml'],
             ['Undo', 'Redo', '-', 'SelectAll', 'Find', '-', 'RemoveFormat'],
             ['Link', 'Unlink', 'Anchor', 'Glossary'],
             [
@@ -252,4 +252,11 @@ class Basic extends Toolbar
         ];
     }
 
+    /**
+     * @return array
+     */
+    public function getNewPageBlock()
+    {
+        return  ['NewPage', 'Templates', '-', 'PasteFromWord', 'inserthtml'];
+    }
 }

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Careers.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * Careers toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class Careers extends Basic
@@ -86,7 +86,7 @@ class Careers extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteText'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Youtube', 'Audio', 'Table'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Documents.php

@@ -94,7 +94,7 @@ class Documents extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord', 'inserthtml'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             [
                 'Link',

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/DocumentsStudent.php

@@ -132,7 +132,7 @@ class DocumentsStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             [
                 'Link',

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/FAQ.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * FAQ toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class FAQ extends Basic
@@ -81,7 +81,7 @@ class FAQ extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Forum.php

@@ -66,7 +66,7 @@ class Forum extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Oembed', 'Flash', 'Youtube', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ForumStudent.php

@@ -92,7 +92,7 @@ class ForumStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Oembed', 'Flash', 'Youtube', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Glossary.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * Glossary toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class Glossary extends Basic
@@ -52,7 +52,7 @@ class Glossary extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', 'NewPage', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/LearningPathDocuments.php

@@ -111,7 +111,7 @@ class LearningPathDocuments extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Youtube', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Messages.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * Messages toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class Messages extends Basic
@@ -94,7 +94,7 @@ class Messages extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/NotebookStudent.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * NotebookStudent toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class NotebookStudent extends Basic
@@ -91,7 +91,7 @@ class NotebookStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/PortalNews.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * PortalNews toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class PortalNews extends Basic
@@ -91,7 +91,7 @@ class PortalNews extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Profile.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * Profile toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class Profile extends Basic
@@ -88,7 +88,7 @@ class Profile extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Project.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * Project toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class Project extends Basic
@@ -88,7 +88,7 @@ class Project extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table',  'Asciimath'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ProjectComment.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * ProjectComment toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class ProjectComment extends Basic
@@ -87,7 +87,7 @@ class ProjectComment extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table',  'Asciimath'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ProjectCommentStudent.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * ProjectCommentStudent toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class ProjectCommentStudent extends Basic
@@ -87,7 +87,7 @@ class ProjectCommentStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table',  'Asciimath'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/ProjectStudent.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * ProjectStudent toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class ProjectStudent extends Basic
@@ -88,7 +88,7 @@ class ProjectStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table',  'Asciimath'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Survey.php

@@ -89,7 +89,7 @@ class Survey extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestAnswerFeedback.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * TestAnswerFeedback toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class TestAnswerFeedback extends Basic
@@ -96,7 +96,7 @@ class TestAnswerFeedback extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestFreeAnswer.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * TestFreeAnswer toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class TestFreeAnswer extends Basic
@@ -96,7 +96,7 @@ class TestFreeAnswer extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteText'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestProposedAnswer.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * TestProposedAnswer toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class TestProposedAnswer extends Basic
@@ -47,7 +47,7 @@ class TestProposedAnswer extends Basic
     {
         return [
             ['Bold', 'Subscript', 'Superscript'],
-            ['Image', 'Link', 'Audio', 'Table', 'PasteFromWord'],
+            ['Image', 'Link', 'Audio', 'Table', 'PasteFromWord', 'inserthtml'],
             ['Maximize', 'Source'],
         ];
     }

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/TestQuestionDescription.php

@@ -123,7 +123,7 @@ class TestQuestionDescription extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Unlink', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Wiki.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * Wiki toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class Wiki extends Basic
@@ -92,7 +92,7 @@ class Wiki extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteText'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Wikilink', 'Link', 'Image', 'Video', 'Flash', 'Audio', 'Table',  'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/WikiStudent.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * WikiStudent toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class WikiStudent extends Basic
@@ -128,7 +128,7 @@ class WikiStudent extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['Save', 'NewPage', 'Templates', '-', 'PasteText'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Wikilink', 'Link', 'Image', 'Video', 'Flash', 'Audio', 'Table', 'Asciimath', 'Asciisvg'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 2 - 2
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/WikiTask.php

@@ -5,7 +5,7 @@ namespace Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar;
 
 /**
  * WikiTask toolbar configuration
- * 
+ *
  * @package Chamilo\CoreBundle\Component\Editor\CkEditor\Toolbar
  */
 class WikiTask extends Basic
@@ -88,7 +88,7 @@ class WikiTask extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Audio', 'Table'],
             ['BulletedList', 'NumberedList', 'HorizontalRule'],

+ 1 - 1
src/Chamilo/CoreBundle/Component/Editor/CkEditor/Toolbar/Work.php

@@ -93,7 +93,7 @@ class Work extends Basic
     protected function getMinimizedToolbar()
     {
         return [
-            ['NewPage', 'Templates', '-', 'PasteFromWord'],
+            $this->getNewPageBlock(),
             ['Undo', 'Redo'],
             ['Link', 'Image', 'Video', 'Flash', 'Youtube', 'Audio', 'Table', 'Asciimath'],
             ['JustifyLeft', 'JustifyCenter', 'JustifyBlock'],

部分文件因文件數量過多而無法顯示