Merge pull request #4368 from AngelFQC/BT18201

Portfolio
pull/4384/head
Angel Fernando Quiroz Campos 3 years ago committed by GitHub
commit f46d87a68f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/Resources/public/css/base.css
  2. 23
      main/course_info/infocours.php
  3. BIN
      main/img/icons/128/eye-slash.png
  4. BIN
      main/img/icons/16/eye-slash.png
  5. BIN
      main/img/icons/22/eye-slash.png
  6. BIN
      main/img/icons/32/eye-slash.png
  7. BIN
      main/img/icons/32/wizard_na.png
  8. BIN
      main/img/icons/48/eye-slash.png
  9. BIN
      main/img/icons/64/eye-slash.png
  10. 161
      main/img/icons/svg/eye-slash.svg
  11. 3
      main/inc/ajax/course.ajax.php
  12. 19
      main/inc/ajax/extra_field.ajax.php
  13. 79
      main/inc/ajax/portfolio.ajax.php
  14. 771
      main/inc/lib/PortfolioController.php
  15. 102
      main/inc/lib/PortfolioNotifier.php
  16. 3
      main/inc/lib/add_course.lib.inc.php
  17. 3
      main/inc/lib/course.lib.php
  18. 2
      main/inc/lib/display.lib.php
  19. 1
      main/inc/lib/extra_field.lib.php
  20. 6
      main/inc/lib/formvalidator/Element/DatePicker.php
  21. 70
      main/inc/lib/formvalidator/Element/SelectAjax.php
  22. 19
      main/inc/lib/hook/HookPortfolioItemViewed.php
  23. 8
      main/inc/lib/hook/interfaces/HookPortfolioItemViewedEventInterface.php
  24. 8
      main/inc/lib/hook/interfaces/HookPortfolioItemViewedObserverInterface.php
  25. 6
      main/inc/lib/sortable_table.class.php
  26. 4
      main/install/configuration.dist.php
  27. 40
      main/portfolio/index.php
  28. 103
      main/template/default/portfolio/items.html.twig
  29. 21
      main/template/default/portfolio/list.html.twig
  30. 121
      main/template/default/portfolio/view.html.twig
  31. 25
      plugin/xapi/src/Hook/XApiPortfolioItemViewedHookObserver.php
  32. 57
      plugin/xapi/src/ToolExperience/Statement/BaseStatement.php
  33. 67
      plugin/xapi/src/ToolExperience/Statement/PortfolioAttachmentsTrait.php
  34. 41
      plugin/xapi/src/ToolExperience/Statement/PortfolioItem.php
  35. 2
      plugin/xapi/src/ToolExperience/Statement/PortfolioItemCommented.php
  36. 57
      plugin/xapi/src/ToolExperience/Statement/PortfolioItemShared.php
  37. 48
      plugin/xapi/src/ToolExperience/Statement/PortfolioItemViewed.php
  38. 6
      plugin/xapi/src/XApiPlugin.php
  39. 68
      src/Chamilo/CoreBundle/Entity/Portfolio.php
  40. 2
      src/Chamilo/CoreBundle/Entity/PortfolioCategory.php
  41. 19
      src/Chamilo/CoreBundle/Entity/PortfolioComment.php
  42. 6
      src/Chamilo/CoreBundle/Entity/Repository/PortfolioRepository.php
  43. 31
      src/Chamilo/CoreBundle/Entity/Repository/TagRepository.php
  44. 2
      src/Chamilo/CoreBundle/Entity/Tag.php

@ -8142,7 +8142,7 @@ input + .progress {
width: 120px; width: 120px;
} }
footer { footer.footer {
font-size: 12px; font-size: 12px;
padding-top: 10px; padding-top: 10px;
} }
@ -9824,7 +9824,7 @@ ul.dropdown-menu.inner > li > a {
} }
@media (min-width: 320px) and (max-width: 479px) { @media (min-width: 320px) and (max-width: 479px) {
footer { footer.footer {
padding-top: 15px; padding-top: 15px;
} }

@ -517,6 +517,23 @@ if ($allowPortfolioTool) {
2 2
); );
$form->addGroup($group, '', [get_lang("EmailToTeachersWhenNewPost")]); $form->addGroup($group, '', [get_lang("EmailToTeachersWhenNewPost")]);
$group = [];
$group[] = $form->createElement(
'radio',
'email_alert_teachers_student_new_comment',
get_lang('EmailToTeachersAndStudentWhenNewComment'),
get_lang('Yes'),
1
);
$group[] = $form->createElement(
'radio',
'email_alert_teachers_student_new_comment',
null,
get_lang('No'),
2
);
$form->addGroup($group, '', [get_lang("EmailToTeachersAndStudentWhenNewComment")]);
} }
$form->addButtonSave(get_lang('SaveSettings'), 'submit_save'); $form->addButtonSave(get_lang('SaveSettings'), 'submit_save');
@ -1015,6 +1032,12 @@ if ($allowPortfolioTool) {
get_lang('MaxScore') => [ get_lang('MaxScore') => [
$form->createElement('number', 'portfolio_max_score', get_lang('MaxScore'), ['step' => 'any', 'min' => 0]), $form->createElement('number', 'portfolio_max_score', get_lang('MaxScore'), ['step' => 'any', 'min' => 0]),
], ],
get_lang('RequiredNumberOfItems') => [
$form->createElement('number', 'portfolio_number_items', '', ['step' => '1', 'min' => 0]),
],
get_lang('RequiredNumberOfComments') => [
$form->createElement('number', 'portfolio_number_comments', '', ['step' => '1', 'min' => 0]),
],
$form->addButtonSave(get_lang('SaveSettings'), 'submit_save', true), $form->addButtonSave(get_lang('SaveSettings'), 'submit_save', true),
]; ];

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="128"
height="128"
viewBox="0 0 128 128"
id="svg10658"
version="1.1"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="eye-slash.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs10660">
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7610"
id="linearGradient7618"
x1="199.92903"
y1="85.563477"
x2="211.45731"
y2="85.563477"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.86698477,0,0,0.86698477,23.39458,15.899244)" />
<linearGradient
id="linearGradient7610"
inkscape:collect="always">
<stop
id="stop13016"
offset="0"
style="stop-color:#0088aa;stop-opacity:1;" />
<stop
id="stop13018"
offset="1"
style="stop-color:#006680;stop-opacity:1" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2"
inkscape:cx="129"
inkscape:cy="56"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1366"
inkscape:window-height="731"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0"
inkscape:snap-bbox="true"
inkscape:snap-center="true"
inkscape:lockguides="true" />
<metadata
id="metadata10663">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-924.36216)">
<g
id="g7343"
transform="matrix(5.3333333,0,0,5.3333333,-1015.2146,511.94192)"
inkscape:export-filename="/var/www/chamilo/main/img/icons/22/eyes.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<rect
ry="0"
y="77.328796"
x="190.35274"
height="24"
width="24"
id="rect6795"
style="opacity:0;fill:#ececec;fill-opacity:1;stroke:none;stroke-opacity:1" />
<g
transform="translate(1.1929383e-6,1.270475e-6)"
id="g7329">
<path
style="fill:#ffffff;stroke:none"
d="m 192.14335,91.247329 c 5.98435,5.011055 14.20046,4.730023 18.77758,-0.506146 -4.3124,-7.39171 -12.87365,-6.685246 -18.86099,-1.02757 -0.7021,0.541768 -0.43847,1.196626 0.0834,1.533716 z"
id="path6799"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
id="svg_117"
d="m 190.85418,90.198509 c 0,0 9.31585,-13.273622 22.65029,-1.857142 0,0 0.60882,0.548175 0.21318,1.248325 -0.49868,0.882631 -1.70499,-0.578496 -1.70499,-0.578496 0,0 -9.25484,-10.959822 -21.15848,1.187313 z"
inkscape:connector-curvature="0" />
<path
id="svg_119"
d="m 205.22627,86.03806 c 0.92486,0.906913 1.49857,2.170679 1.49857,3.568316 0,2.760015 -2.23741,4.997418 -4.99741,4.997418 -2.76003,0 -4.99743,-2.237403 -4.99743,-4.997418 0,-1.344005 0.53064,-2.564272 1.39383,-3.462105 0,0 1.38645,-0.585112 3.6036,-0.585112 2.21712,0 3.49884,0.478901 3.49884,0.478901 z m -5.88174,3.362228 -1.96163,-0.181594 c -0.0113,0.12796 -0.0196,0.256977 -0.0196,0.387894 0,0.686677 0.16301,1.333658 0.44554,1.911591 l 1.77181,-0.858348 c -0.15647,-0.318212 -0.24663,-0.674642 -0.24663,-1.053033 2e-4,-0.0701 0.005,-0.138517 0.0106,-0.20651 z m 2.34762,-1.588944 c -1.01079,0 -1.83008,0.819284 -1.83008,1.830084 0,1.010801 0.81929,1.830085 1.83008,1.830085 1.0108,0 1.83009,-0.819283 1.83009,-1.830085 0,-1.010801 -0.81929,-1.830084 -1.83009,-1.830084 z"
inkscape:connector-curvature="0"
style="fill:url(#linearGradient7618);fill-opacity:1" />
<path
id="svg_118"
d="m 191.58483,90.168006 c 0,0 10.22917,-11.294645 19.87984,-0.426155 0,0 1.43098,1.765627 0.48716,2.526786 -0.94382,0.761159 -1.43098,-0.213168 -1.67447,-0.365326 -0.2435,-0.15216 -0.39584,0.09133 -0.5785,0.395831 -0.18266,0.3045 -8.92002,6.606397 -17.99237,-1.369977 0,0 9.83333,7.915547 17.74888,0.517668 0,0 0.88299,-0.974145 -0.0303,-2.100632 -0.91332,-1.126486 -6.39341,-8.189727 -17.84022,0.821805 z"
inkscape:connector-curvature="0"
style="fill:#000000" />
<circle
r="2.0291471"
cy="89.728737"
cx="201.61891"
style="fill:#000000;fill-opacity:1;stroke:none"
id="path7627"
inkscape:export-filename="/var/www/chamilo/main/img/icons/32/visible.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
</g>
</g>
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.24539;stroke-opacity:1"
id="rect15804"
width="12.108109"
height="104"
x="547.42493"
y="771.96112"
transform="rotate(30.078781)" />
<path
id="rect15981"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.160646;stroke-opacity:1"
transform="translate(0,924.36216)"
d="m 94.03125,40.289062 c 0.03225,0.176784 0.07362,0.349276 0.144531,0.509766 -0.220186,-0.08522 -0.362933,-0.138266 -0.46289,-0.199219 l -30.574219,52.78711 c 0.06363,-0.0082 0.117676,-0.01507 0.189453,-0.02539 0.408593,0.06455 0.466826,0.07156 0.523437,0.08789 0.08692,-0.01245 0.17593,-0.0083 0.263672,-0.01367 0.04824,0.03894 0.102742,0.07176 0.152344,0.109375 0.657826,-0.423215 1.768688,-0.718578 3.333984,-0.712891 0.538635,0.002 0.920304,-0.01997 1.195313,-0.02344 0.01719,-0.674291 0.04467,-1.170406 0.08398,-1.210938 0.03626,-0.03739 0.08163,0.307095 0.136719,1.214844 0.200872,0.01204 0.307449,0.0568 0.353516,0.169922 l 29.048828,-50.15625 c -0.145844,0.0089 -0.26597,0.0028 -0.375,-0.01367 -0.87843,1.144574 -1.672862,1.932916 -1.527344,-0.460938 0.0044,-0.0717 0.008,-0.193654 0.01367,-0.318359 -0.05991,-0.03988 -0.119388,-0.07813 -0.183594,-0.128906 -0.387748,-0.02539 -0.610037,-0.07193 -0.796875,-0.228516 -0.01547,4.03e-4 -0.02796,-0.0038 -0.04297,-0.0039 -0.681922,1.451327 -1.104922,0.486426 -1.300781,-0.814453 -0.0094,-0.01931 -0.02254,-0.03698 -0.03125,-0.05664 0.0066,0.0025 0.01662,0.0071 0.02344,0.0098 -0.02224,-0.152888 -0.04257,-0.308755 -0.05859,-0.466797 -0.03865,-0.01841 -0.07221,-0.03612 -0.109375,-0.05469 z m 0.109375,0.05469 c 0.0044,0.0021 0.0093,0.0038 0.01367,0.0059 -0.0052,-0.0087 -0.01036,-0.01465 -0.01563,-0.02344 5.88e-4,0.0059 0.0014,0.01169 0.002,0.01758 z m 4.265625,1.990234 c -0.04966,0.06674 -0.09871,0.130851 -0.148438,0.197266 0.0151,0.02234 0.02673,0.04671 0.04297,0.06836 0.02153,0.02871 0.11073,0.04922 0.210938,0.06836 l 0.0957,-0.166016 z M 63.080078,93.738281 c -0.04695,-9.79e-4 -0.09307,0.0013 -0.138672,0.0078 0.168032,0.122303 0.341798,0.267122 0.517578,0.417968 0.0637,-0.0088 0.1402,-0.0091 0.210938,-0.01367 0.02889,-0.05428 0.06393,-0.107307 0.103516,-0.160157 -0.226808,-0.140381 -0.464302,-0.247177 -0.69336,-0.251953 z" />
<path
id="rect15983"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.160646;stroke-opacity:1"
transform="translate(0,924.36216)"
d="m 103.0957,45.40625 c 0.26344,0.195909 0.43929,0.352097 0.54492,0.472656 0.002,-0.07817 0.004,-0.156791 0.01,-0.228515 -0.23451,-0.144886 -0.41494,-0.209704 -0.55469,-0.244141 z m 0.67774,0.328125 c -5.1e-4,0.128406 -0.003,0.237128 -0.008,0.322266 0.0402,0.08924 0.03,0.150227 -0.0117,0.189453 -0.004,0.05331 -0.0113,0.123747 -0.0117,0.158203 0.0871,0.01802 0.0443,0.0087 0.21875,0.04687 0.11996,-0.07481 0.24254,-0.14428 0.36718,-0.212891 -0.14942,-0.148756 -0.3015,-0.292497 -0.45703,-0.433593 -0.0344,-0.02514 -0.0647,-0.04812 -0.0977,-0.07031 z m -1.08203,0.06641 -0.0957,0.167969 c 0.41062,0.232805 0.80652,0.435532 1.11133,0.576172 -0.007,-0.03431 -0.0149,-0.06953 -0.0215,-0.105469 -0.0732,-0.03166 -0.14546,-0.06452 -0.21875,-0.0957 0.008,0.0035 0.14713,0.0321 0.20898,0.04492 -0.006,-0.03258 -0.0124,-0.05975 -0.0176,-0.09375 -0.0904,0.02766 -0.18206,0.03125 -0.22265,0.0332 -0.22911,-0.09849 -0.45184,-0.205033 -0.625,-0.378906 -0.0366,-0.0367 -0.0775,-0.08885 -0.11914,-0.148438 z m -0.16602,0.289063 -25.855468,44.640625 c 0.254581,-0.105524 0.520186,-0.18379 0.798828,-0.220703 0.242856,-0.043 0.48383,-0.09798 0.728516,-0.128907 0.234421,-0.02963 -0.113588,0.201809 0.132812,0.103516 0.276252,-0.412461 0.490152,-0.621337 0.480469,-0.251953 0.144583,-0.09727 0.284363,-0.203189 0.435547,-0.289063 1.049558,-0.596158 1.26622,-0.679273 2.09375,-1.027343 0.338194,-0.454294 0.728367,-0.662779 1.115234,-0.673828 0.160373,-0.0046 0.318228,0.03388 0.472656,0.08984 0.457342,-0.122754 0.874794,-0.189636 1.248047,-0.207031 L 107.00781,48.705078 c -0.0262,-0.03187 -0.0536,-0.06276 -0.0781,-0.0957 -0.06,-0.02704 -0.11889,-0.06505 -0.17774,-0.103516 -0.38205,0.04151 -0.65386,0.01935 -0.86328,-0.07227 -0.17411,0.04627 -0.3232,0.07092 -0.44922,0.07422 0.27084,0.132368 0.70137,0.208985 0.50391,0.226563 -0.17247,0.01535 -0.40714,-0.09363 -0.63477,-0.228516 -0.23782,-0.02296 -0.37725,-0.134152 -0.45898,-0.300781 -0.0254,-0.01755 -0.0574,-0.03925 -0.0801,-0.05469 -0.15346,-0.144933 -0.31357,-0.284432 -0.46094,-0.435547 -0.20147,-0.206596 -0.34294,-0.406378 -0.44531,-0.638672 -0.59638,-0.200733 -1.03822,-0.523978 -1.33789,-0.986328 z m 1.22266,0.376953 c 0.006,0.03596 0.0163,0.07183 0.0391,0.113281 0.30534,0.13692 0.50997,0.213034 0.375,0.113281 -0.12571,-0.09291 -0.26905,-0.16173 -0.41406,-0.226562 z m 0.95312,0.152344 c 0.003,0.151195 0.005,0.342043 0.0215,0.382812 0.0146,-0.0082 0.0289,-0.01524 0.0469,-0.02148 0.0138,-0.08505 0.0185,-0.108036 0.0273,-0.160157 -0.008,-0.04295 -0.0161,-0.0859 -0.0234,-0.128906 -0.0268,-0.02708 -0.0547,-0.05436 -0.0723,-0.07227 z m 0.0684,0.361328 c -0.003,0.01664 -0.003,0.01323 -0.006,0.03125 -4.3e-4,0.0087 -0.002,0.01674 -0.002,0.02539 0.0191,0.0093 0.0409,0.01597 0.0762,0.02148 -0.006,-0.0316 -0.01,-0.06401 -0.0156,-0.0957 -0.019,0.0064 -0.0316,0.0091 -0.0508,0.01563 z m -0.008,0.05664 c -0.0154,-0.0075 -0.0311,-0.01581 -0.0391,-0.03516 -0.0508,0.02851 -0.0826,0.07047 -0.0977,0.119141 0.0443,5.89e-4 0.0885,0.0032 0.13281,0.0039 0.002,-0.02974 0.002,-0.05799 0.004,-0.08789 z m 0.19922,-0.11914 c -0.0335,0.01116 -0.0576,0.01783 -0.0918,0.0293 0.006,0.04278 0.0132,0.08469 0.0234,0.125 0.0407,0.0079 0.0828,0.01687 0.14453,0.04102 -0.023,-0.05933 -0.0517,-0.131698 -0.0762,-0.195312 z m -0.20313,0.234375 c -0.0352,0.0042 -0.0702,0.0054 -0.10547,0.0098 0.0339,0.01532 0.0689,0.03489 0.10157,0.04883 9.6e-4,-0.01969 0.003,-0.03879 0.004,-0.05859 z m -27.365232,43.6875 c -0.186387,9.9e-4 -0.332837,0.394456 -0.46875,0.935547 0.09759,0.02125 0.195397,0.0426 0.285156,0.07227 0.13654,-0.05829 0.273569,-0.09911 0.41211,-0.132812 0.05743,-0.114649 0.122169,-0.223847 0.183594,-0.339844 -0.15862,-0.375358 -0.293515,-0.535786 -0.41211,-0.535156 z m -0.628906,0.09375 c -0.06526,0.0036 -0.147753,0.01877 -0.232422,0.03516 l -0.449219,0.777344 c 0.100209,-0.01356 0.174389,-0.02332 0.31836,-0.04492 0.0055,3.1e-4 0.01014,0.0016 0.01563,0.002 0.185055,-0.199658 0.354188,-0.412387 0.492187,-0.648437 0.0587,-0.100409 -0.0142,-0.12834 -0.144531,-0.121094 z" />
<path
id="rect15985"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.160646;stroke-opacity:1"
d="M 111.43164,51.417969 91.826172,85.269531 c 0.184133,-0.210982 0.414525,-0.322177 0.671875,-0.328125 0.306294,-0.0071 0.650661,0.13892 1,0.435547 0.247967,-0.119068 0.486544,-0.256623 0.722656,-0.398437 -0.394865,-2.193256 0.305697,-3.365599 1.220703,-3.353516 0.612429,0.008 1.309064,0.575562 1.839844,1.6875 1.025471,-0.969823 1.936629,-1.500434 2.654297,-1.689453 L 115.45703,54.826172 c -0.44746,0.02798 -1.2865,-0.370414 -3.00976,-1.082031 -0.0467,-0.0193 -0.0257,-0.142426 0.0254,-0.302735 -0.0132,-0.01846 -0.0229,-0.02992 -0.041,-0.05664 -0.49321,-0.726227 -0.81489,-1.387947 -1,-1.966797 z m 1.12695,1.789062 c -0.0481,0.0073 -0.0539,0.06106 -0.0469,0.123047 0.0151,-0.04144 0.0295,-0.07988 0.0469,-0.123047 z"
transform="translate(0,924.36216)" />
<path
id="rect15987"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.160646;stroke-opacity:1"
transform="translate(0,924.36216)"
d="m 120.42773,56.597656 -11.90429,20.552735 c 0.28243,0.09783 0.59682,0.290418 0.92383,0.535156 0.29128,-0.401499 0.70167,-0.60744 1.14257,-0.580078 0.84284,0.0523 1.79482,0.959602 2.23438,2.976562 l 11.00976,-19.011719 c -0.28956,-0.389003 -0.56466,-0.889383 -0.80664,-1.505859 -0.79022,-1.054919 -1.64123,-2.066034 -2.59961,-2.966797 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

@ -303,6 +303,9 @@ switch ($action) {
$json['items'][] = [ $json['items'][] = [
'id' => $user['user_id'], 'id' => $user['user_id'],
'text' => "{$user['username']} ($userCompleteName)", 'text' => "{$user['username']} ($userCompleteName)",
'avatarUrl' => UserManager::getUserPicture($user['id']),
'username' => $user['username'],
'completeName' => $userCompleteName,
]; ];
} }

@ -36,7 +36,9 @@ switch ($action) {
break; break;
case 'search_tags': case 'search_tags':
header('Content-Type: application/json'); header('Content-Type: application/json');
$tag = isset($_REQUEST['q']) ? $_REQUEST['q'] : null; $tag = $_REQUEST['q'] ?? null;
$pageLimit = isset($_REQUEST['page_limit']) ? (int) $_REQUEST['page_limit'] : 10;
$byId = !empty($_REQUEST['byid']);
$result = []; $result = [];
if (empty($tag)) { if (empty($tag)) {
@ -44,22 +46,15 @@ switch ($action) {
exit; exit;
} }
$extraFieldOption = new ExtraFieldOption($type);
$tags = Database::getManager() $tags = Database::getManager()
->getRepository('ChamiloCoreBundle:Tag') ->getRepository(Tag::class)
->createQueryBuilder('t') ->findByFieldIdAndText($fieldId, $tag, $pageLimit)
->where("t.tag LIKE :tag") ;
->andWhere('t.fieldId = :field')
->setParameter('field', $fieldId)
->setParameter('tag', "$tag%")
->getQuery()
->getResult();
/** @var Tag $tag */ /** @var Tag $tag */
foreach ($tags as $tag) { foreach ($tags as $tag) {
$result[] = [ $result[] = [
'id' => $tag->getTag(), 'id' => $byId ? $tag->getId() : $tag->getTag(),
'text' => $tag->getTag(), 'text' => $tag->getTag(),
]; ];
} }

@ -0,0 +1,79 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\CoreBundle\Entity\PortfolioComment;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Symfony\Component\HttpFoundation\Response;
require_once __DIR__.'/../global.inc.php';
$httpRequest = HttpRequest::createFromGlobals();
$action = $httpRequest->query->has('a') ? $httpRequest->query->get('a') : $httpRequest->request->get('a');
$currentUserId = api_get_user_id();
$currentUser = api_get_user_entity($currentUserId);
$em = Database::getManager();
$item = null;
$comment = null;
if ($httpRequest->query->has('item')) {
/** @var Portfolio $item */
$item = $em->find(
Portfolio::class,
$httpRequest->query->getInt('item')
);
}
if ($httpRequest->query->has('comment')) {
$comment = $em->find(
PortfolioComment::class,
$httpRequest->query->getInt('comment')
);
}
$httpResponse = Response::create();
switch ($action) {
case 'find_template':
if (!$item) {
$httpResponse->setStatusCode(Response::HTTP_NOT_FOUND);
break;
}
if (!$item->isTemplate() || $item->getUser() !== $currentUser) {
$httpResponse->setStatusCode(Response::HTTP_FORBIDDEN);
break;
}
$httpResponse = JsonResponse::create(
[
'title' => $item->getTitle(),
'content' => $item->getContent(),
]
);
break;
case 'find_template_comment':
if (!$comment) {
$httpResponse->setStatusCode(Response::HTTP_NOT_FOUND);
break;
}
if (!$comment->isTemplate() || $comment->getAuthor() !== $currentUser) {
$httpResponse->setStatusCode(Response::HTTP_FORBIDDEN);
break;
}
$httpResponse = JsonResponse::create(
[
'content' => $comment->getContent(),
]
);
break;
}
$httpResponse->send();

File diff suppressed because it is too large Load Diff

@ -0,0 +1,102 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Course as CourseEntity;
use Chamilo\CoreBundle\Entity\PortfolioComment;
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
class PortfolioNotifier
{
public static function notifyTeachersAndAuthor(PortfolioComment $comment)
{
$item = $comment->getItem();
$course = $item->getCourse();
$session = $item->getSession();
$messageSubject = sprintf(
get_lang('PortfolioAlertNewCommentSubject'),
$item->getTitle(true)
);
$userIdListToSend = [];
$userIdListToSend[] = $comment->getItem()->getUser()->getId();
$cidreq = api_get_cidreq_params(
$course ? $course->getCode() : '',
$session ? $session->getId() : 0
);
$commentUrl = api_get_path(WEB_CODE_PATH).'portfolio/index.php?'
.($course ? $cidreq.'&' : '')
.http_build_query(['action' => 'view', 'id' => $item->getId()])."#comment-{$comment->getId()}";
if ($course) {
$courseInfo = api_get_course_info($course->getCode());
if (1 !== (int) api_get_course_setting('email_alert_teachers_student_new_comment', $courseInfo)) {
return;
}
$courseTitle = self::getCourseTitle($course, $session);
$userIdListToSend = array_merge(
$userIdListToSend,
self::getTeacherList($course, $session)
);
$messageContent = sprintf(
get_lang('CoursePortfolioAlertNewCommentContent'),
$item->getTitle(),
$courseTitle,
$commentUrl
);
} else {
$messageContent = sprintf(
get_lang('PortfolioAlertNewCommentContent'),
$item->getTitle(),
$commentUrl
);
}
$messageContent .= '<br><br><figure>'
.'<blockquote>'.$comment->getExcerpt().'</blockquote>'
.'<figcaption>'.$comment->getAuthor()->getCompleteName().'</figcaption>'
.'</figure>';
foreach ($userIdListToSend as $userIdToSend) {
MessageManager::send_message_simple(
$userIdToSend,
$messageSubject,
$messageContent,
0,
false,
false,
[],
false
);
}
}
private static function getCourseTitle(CourseEntity $course, ?SessionEntity $session = null): string
{
if ($session) {
return "{$course->getTitle()} ({$session->getName()})";
}
return $course->getTitle();
}
private static function getTeacherList(CourseEntity $course, ?SessionEntity $session = null): array
{
if ($session) {
$teachers = SessionManager::getCoachesByCourseSession(
$session->getId(),
$course->getId()
);
return array_values($teachers);
}
$teachers = CourseManager::get_teacher_list_from_course_code($course->getCode());
return array_keys($teachers);
}
}

@ -662,7 +662,8 @@ class AddCourse
'documents_default_visibility' => ['default' => 'visible', 'category' => 'document'], 'documents_default_visibility' => ['default' => 'visible', 'category' => 'document'],
'show_course_in_user_language' => ['default' => 2, 'category' => null], 'show_course_in_user_language' => ['default' => 2, 'category' => null],
'email_to_teachers_on_new_work_feedback' => ['default' => 1, 'category' => null], 'email_to_teachers_on_new_work_feedback' => ['default' => 1, 'category' => null],
'email_alert_teachers_new_post' => ['default' => 2, 'category' => 'portfolio'], 'email_alert_teachers_new_post' => ['default' => 1, 'category' => 'portfolio'],
'email_alert_teachers_student_new_comment' => ['default' => 1, 'category' => 'portfolio'],
'agenda_share_events_in_sessions' => ['default' => 0, 'category' => 'agenda'], 'agenda_share_events_in_sessions' => ['default' => 0, 'category' => 'agenda'],
]; ];

@ -5840,9 +5840,12 @@ class CourseManager
if (api_get_configuration_value('allow_portfolio_tool')) { if (api_get_configuration_value('allow_portfolio_tool')) {
$courseSettings[] = 'email_alert_teachers_new_post'; $courseSettings[] = 'email_alert_teachers_new_post';
$courseSettings[] = 'email_alert_teachers_student_new_comment';
$courseSettings[] = 'qualify_portfolio_item'; $courseSettings[] = 'qualify_portfolio_item';
$courseSettings[] = 'qualify_portfolio_comment'; $courseSettings[] = 'qualify_portfolio_comment';
$courseSettings[] = 'portfolio_max_score'; $courseSettings[] = 'portfolio_max_score';
$courseSettings[] = 'portfolio_number_items';
$courseSettings[] = 'portfolio_number_comments';
} }
if (api_get_configuration_value('lp_show_max_progress_or_average_enable_course_level_redefinition')) { if (api_get_configuration_value('lp_show_max_progress_or_average_enable_course_level_redefinition')) {

@ -2769,7 +2769,7 @@ HTML;
/** /**
* Returns the string "1 day ago" with a link showing the exact date time. * Returns the string "1 day ago" with a link showing the exact date time.
* *
* @param string $dateTime in UTC or a DateTime in UTC * @param string|DateTime $dateTime in UTC or a DateTime in UTC
* *
* @return string * @return string
*/ */

@ -656,6 +656,7 @@ class ExtraField extends Model
$row['variable'], $row['variable'],
$row['display_text'] $row['display_text']
); );
$row['options'] = [];
// All the tags of the field // All the tags of the field
$sql = "SELECT * FROM $this->table_field_tag $sql = "SELECT * FROM $this->table_field_tag

@ -122,6 +122,12 @@ class DatePicker extends HTML_QuickForm_text
case FormValidator::LAYOUT_BOX_NO_LABEL: case FormValidator::LAYOUT_BOX_NO_LABEL:
return '{element}'; return '{element}';
} }
return '<div class="form-group">
<label {label-for}>{label}</label>
{element}
</div>'
;
} }
/** /**

@ -24,12 +24,17 @@ class SelectAjax extends HTML_QuickForm_select
{ {
$iso = api_get_language_isocode(api_get_interface_language()); $iso = api_get_language_isocode(api_get_interface_language());
$formatResult = $this->getAttribute('formatResult'); $formatResult = $this->getAttribute('formatResult');
$formatSelection = $this->getAttribute('formatSelection');
$formatCondition = ''; $formatCondition = '';
if (!empty($formatResult)) { if (!empty($formatResult)) {
$formatCondition = ', $formatCondition .= ',
templateResult : '.$formatResult.', templateResult : '.$formatResult;
templateSelection : '.$formatResult; }
if (!empty($formatSelection)) {
$formatCondition .= ',
templateSelection : '.$formatSelection;
} }
$width = 'element'; $width = 'element';
@ -109,14 +114,15 @@ class SelectAjax extends HTML_QuickForm_select
results: '' results: ''
}; };
} }
$formatCondition
} }
$formatCondition
}); });
}); });
</script> </script>
JS; JS;
$this->removeAttribute('formatResult'); $this->removeAttribute('formatResult');
$this->removeAttribute('formatSelection');
$this->removeAttribute('minimumInputLength'); $this->removeAttribute('minimumInputLength');
$this->removeAttribute('maximumSelectionLength'); $this->removeAttribute('maximumSelectionLength');
$this->removeAttribute('tags'); $this->removeAttribute('tags');
@ -143,4 +149,60 @@ JS;
return $this->_prepareValue($value, $assoc); return $this->_prepareValue($value, $assoc);
} }
public static function templateResultForUsersInCourse(): string
{
return "function (state) {
if (state.loading) {
return state.text;
}
var \$container = \$(
'<div class=\"select2-result-user clearfix\">' +
'<div class=\"select2-result-user__avatar pull-left\">' +
'<img>' +
'</div>' +
'<div class=\"select2-result-user__info pull-left\">' +
'<div class=\"select2-result-user__name\"></div>' +
'<div class=\"select2-result-user__username small\"></div>' +
'</div>' +
'</div>'
);
\$container.find('.select2-result-user__avatar img')
.prop({ 'src': state.avatarUrl, 'alt': state.username })
.css({ 'width': '40px', 'height': '40px' });
\$container.find('.select2-result-user__info').css({ 'paddingLeft': '6px' });
\$container.find('.select2-result-user__name').text(state.completeName);
\$container.find('.select2-result-user__username').text(state.username);
return \$container;
}";
}
public static function templateSelectionForUsersInCourse(): string
{
return "function (state) {
if (!state.id) {
return state.text;
}
if (!state.avatarUrl) {
var avatarUrl = $(state.element).data('avatarurl');
var username = $(state.element).data('username');
state.avatarUrl = avatarUrl;
state.username = username;
state.completeName = state.text;
}
var \$container = \$('<span><img> ' + state.completeName + '</span>');
\$container.find('img')
.prop({ 'src': state.avatarUrl, 'alt': state.username })
.css({ 'width': '20px', 'height': '20px' });
return \$container;
}";
}
} }

@ -0,0 +1,19 @@
<?php
/* For licensing terms, see /license.txt */
class HookPortfolioItemViewed extends HookEvent implements HookPortfolioItemViewedEventInterface
{
protected function __construct()
{
parent::__construct('HookPortfolioItemViewed');
}
public function notifyItemViewed(): void
{
/** @var HookPortfolioItemViewedObserverInterface $observer */
foreach ($this->observers as $observer) {
$observer->hookItemViewed($this);
}
}
}

@ -0,0 +1,8 @@
<?php
/* For licensing terms, see /license.txt */
interface HookPortfolioItemViewedEventInterface extends HookEventInterface
{
public function notifyItemViewed(): void;
}

@ -0,0 +1,8 @@
<?php
/* For licensing terms, see /license.txt */
interface HookPortfolioItemViewedObserverInterface extends HookObserverInterface
{
public function hookItemViewed(HookPortfolioItemViewedEventInterface $hookEvent);
}

@ -1003,9 +1003,9 @@ class SortableTable extends HTML_Table
* Add a filter to a column. If another filter was already defined for the * Add a filter to a column. If another filter was already defined for the
* given column, it will be overwritten. * given column, it will be overwritten.
* *
* @param int $column The number of the column * @param int $column The number of the column
* @param string $function The name of the filter-function. This should be a * @param string|Closure $function The name of the filter-function. This should be a
* function wich requires 1 parameter and returns the filtered value. * function wich requires 1 parameter and returns the filtered value.
*/ */
public function set_column_filter($column, $function) public function set_column_filter($column, $function)
{ {

@ -1054,11 +1054,15 @@ ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062A76ED395 FOREIGN KEY (user_id) R
ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106291D79BD3 FOREIGN KEY (c_id) REFERENCES course (id) ON DELETE CASCADE; ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106291D79BD3 FOREIGN KEY (c_id) REFERENCES course (id) ON DELETE CASCADE;
ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062613FECDF FOREIGN KEY (session_id) REFERENCES session (id) ON DELETE CASCADE; ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED1062613FECDF FOREIGN KEY (session_id) REFERENCES session (id) ON DELETE CASCADE;
ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106212469DE2 FOREIGN KEY (category_id) REFERENCES portfolio_category (id) ON DELETE SET NULL; ALTER TABLE portfolio ADD CONSTRAINT FK_A9ED106212469DE2 FOREIGN KEY (category_id) REFERENCES portfolio_category (id) ON DELETE SET NULL;
ALTER TABLE portfolio CHANGE is_visible visibility SMALLINT DEFAULT 1 NOT NULL;
ALTER TABLE portfolio ADD is_highlighted TINYINT(1) DEFAULT '0' NOT NULL;
ALTER TABLE portfolio ADD is_template TINYINT(1) DEFAULT '0' NOT NULL;
ALTER TABLE portfolio_category ADD CONSTRAINT FK_7AC64359A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE; ALTER TABLE portfolio_category ADD CONSTRAINT FK_7AC64359A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE;
ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2F675F31B FOREIGN KEY (author_id) REFERENCES user (id) ON DELETE CASCADE; ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2F675F31B FOREIGN KEY (author_id) REFERENCES user (id) ON DELETE CASCADE;
ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2126F525E FOREIGN KEY (item_id) REFERENCES portfolio (id) ON DELETE CASCADE; ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2126F525E FOREIGN KEY (item_id) REFERENCES portfolio (id) ON DELETE CASCADE;
ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2A977936C FOREIGN KEY (tree_root) REFERENCES portfolio_comment (id) ON DELETE CASCADE; ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2A977936C FOREIGN KEY (tree_root) REFERENCES portfolio_comment (id) ON DELETE CASCADE;
ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2727ACA70 FOREIGN KEY (parent_id) REFERENCES portfolio_comment (id) ON DELETE CASCADE; ALTER TABLE portfolio_comment ADD CONSTRAINT FK_C2C17DA2727ACA70 FOREIGN KEY (parent_id) REFERENCES portfolio_comment (id) ON DELETE CASCADE;
ALTER TABLE portfolio_comment ADD is_template TINYINT(1) DEFAULT '0' NOT NULL;
ALTER TABLE portfolio_category ADD parent_id INT(11) NOT NULL DEFAULT 0; ALTER TABLE portfolio_category ADD parent_id INT(11) NOT NULL DEFAULT 0;
*/ */
// In 1.11.8, before enabling this feature, you also need to: // In 1.11.8, before enabling this feature, you also need to:

@ -126,8 +126,7 @@ switch ($action) {
$controller->editItem($item); $controller->editItem($item);
return; return;
case 'hide_item': case 'visibility':
case 'show_item':
$id = $httpRequest->query->getInt('id'); $id = $httpRequest->query->getInt('id');
/** @var Portfolio $item */ /** @var Portfolio $item */
@ -271,6 +270,43 @@ switch ($action) {
case 'delete_attachment': case 'delete_attachment':
$controller->deleteAttachment($httpRequest); $controller->deleteAttachment($httpRequest);
break; break;
case 'highlighted':
api_protect_teacher_script();
$id = $httpRequest->query->getInt('id');
/** @var Portfolio $item */
$item = $em->find('ChamiloCoreBundle:Portfolio', $id);
if (empty($item)) {
break;
}
$controller->markAsHighlighted($item);
break;
case 'template':
$id = $httpRequest->query->getInt('id');
/** @var Portfolio $item */
$item = $em->find('ChamiloCoreBundle:Portfolio', $id);
if (empty($item)) {
break;
}
$controller->markAsTemplate($item);
break;
case 'template_comment':
$id = $httpRequest->query->getInt('id');
$comment = $em->find(PortfolioComment::class, $id);
if (empty($comment)) {
break;
}
$controller->markAsTemplateComment($comment);
break;
case 'list': case 'list':
default: default:
$controller->index($httpRequest); $controller->index($httpRequest);

@ -5,34 +5,61 @@
{% set delete_img = 'delete.png'|img(22, 'Delete'|get_lang) %} {% set delete_img = 'delete.png'|img(22, 'Delete'|get_lang) %}
{% set baseurl = _p.web_self ~ '?' ~ (_p.web_cid_query ? _p.web_cid_query ~ '&' : '') %} {% set baseurl = _p.web_self ~ '?' ~ (_p.web_cid_query ? _p.web_cid_query ~ '&' : '') %}
<div class="row portfolio-items"> <div class="portfolio-items">
{% for item in items %} {% for item in items %}
{% set item_url = baseurl ~ {'action':'view', 'id':item.id}|url_encode %} {% set item_url = baseurl ~ {'action':'view', 'id':item.id}|url_encode %}
{% set comments = item.lastComments %} {% set comments = item.lastComments %}
<div class="col-md-12" id="portfolio-item-{{ item.id }}"> <div class="panel panel-default">
<div class="thumbnail"> <article class="panel-body portfolio-item" id="portfolio-item-{{ item.id }}">
<div class="caption"> <div class="portfolio-actions pull-right">
{% if _u.id == item.user.id %} {% if _u.id == item.user.id %}
<div class="portfolio-actions pull-right"> <a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}">
<a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}"> {{ 'edit.png'|img(22, 'Edit'|get_lang) }}
{{ 'edit.png'|img(22, 'Edit'|get_lang) }} </a>
{% if item.visibility == 0 %}
<a href="{{ baseurl ~ {'action':'visibility', 'id':item.id}|url_encode }}">
{{ 'invisible.png'|img(22, 'MakeVisible'|get_lang) }}
</a> </a>
{% if item.isVisible %} {% elseif item.visibility == 1 %}
<a href="{{ baseurl ~ {'action':'hide_item', 'id':item.id}|url_encode }}"> <a href="{{ baseurl ~ {'action':'visibility', 'id':item.id}|url_encode }}">
{{ 'visible.png'|img(22, 'Invisible'|get_lang) }} {{ 'visible.png'|img(22, 'MakeVisibleForTeachers'|get_lang) }}
</a> </a>
{% else %} {% elseif item.visibility == 2 %}
<a href="{{ baseurl ~ {'action':'show_item', 'id':item.id}|url_encode }}"> <a href="{{ baseurl ~ {'action':'visibility', 'id':item.id}|url_encode }}">
{{ 'invisible.png'|img(22, 'Visible'|get_lang) }} {{ 'eye-slash.png'|img(22, 'MakeInvisible'|get_lang) }}
</a>
{% endif %}
<a href="{{ baseurl ~ {'action':'delete_item', 'id':item.id}|url_encode }}" class="btn-delete">
{{ 'delete.png'|img(22, 'Delete'|get_lang) }}
</a> </a>
</div> {% endif %}
<a href="{{ baseurl ~ {'action':'delete_item', 'id':item.id}|url_encode }}" class="btn-delete">
{{ 'delete.png'|img(22, 'Delete'|get_lang) }}
</a>
{% elseif false|api_is_allowed_to_edit %}
<a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}">
{{ 'edit.png'|img(22, 'Edit'|get_lang) }}
</a>
{% endif %} {% endif %}
{% if false|api_is_allowed_to_edit %}
{% if item.isHighlighted %}
<a href="{{ baseurl ~ {'action':'highlighted', 'id':item.id}|url_encode }}">
{{ 'award_red.png'|img(22, 'UnmarkAsHighlighted'|get_lang) }}
</a>
{% else %}
<a href="{{ baseurl ~ {'action':'highlighted', 'id':item.id}|url_encode }}">
{{ 'award_red_na.png'|img(22, 'MarkAsHighlighted'|get_lang) }}
</a>
{% endif %}
{% endif %}
</div>
{% if item.isHighlighted %}
<span class="label label-success">
<span class="fa fa-fw fa-star-o" aria-hidden="true"></span>
{{ 'Highlighted'|get_lang }}
</span>
{% endif %}
<header>
<h3> <h3>
<a href="{{ item_url }}">{{ item.title|remove_xss }}</a> <a href="{{ item_url }}">{{ item.title|remove_xss }}</a>
</h3> </h3>
@ -62,20 +89,22 @@
{{ 'CreationDate'|get_lang ~ ': ' ~ item.creationDate|date_to_time_ago }} {{ 'CreationDate'|get_lang ~ ': ' ~ item.creationDate|date_to_time_ago }}
</li> </li>
{% if _u.id == item.user.id and item.creationDate != item.updateDate %} {% if item.creationDate != item.updateDate %}
<li> <li>
<span class="fa-li fa fa-clock-o" aria-hidden="true"></span> <span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
{{ 'UpdateDate'|get_lang ~ ': ' ~ item.updateDate|date_to_time_ago }} {{ 'UpdateDate'|get_lang ~ ': ' ~ item.updateDate|date_to_time_ago }}
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</header>
<hr> <hr>
<p>{{ item.excerpt }}</p> <p>{{ item.excerpt }}</p>
<hr> <hr>
<section>
{% if comments|length > 0 %} {% if comments|length > 0 %}
<h4> <h4>
<a href="{{ item_url }}#portfolio-item-comments"> <a href="{{ item_url }}#portfolio-item-comments">
@ -84,27 +113,25 @@
&centerdot; &centerdot;
<a href="{{ item_url }}#frm_comment">{{ 'AddNewComment'|get_lang }}</a> <a href="{{ item_url }}#frm_comment">{{ 'AddNewComment'|get_lang }}</a>
</h4> </h4>
<ul class="fa-ul"> {% for comment in comments %}
{% for comment in comments %} <article>
<li> <footer>
<span class="fa-li fa fa-commenting-o" aria-hidden="true"></span> <span class="fa fa-fw fa-commenting-o" aria-hidden="true"></span>
<div> <strong>{{ comment.author.completeName }}</strong>
<strong>{{ comment.author.completeName }}</strong> <small class="fa fa-clock-o fa-fw text-muted" aria-hidden="true"></small>
<small class="fa fa-clock-o fa-fw text-muted" aria-hidden="true"></small> <small class="text-muted">{{ comment.date|date_to_time_ago }}</small>
<small class="text-muted">{{ comment.date|date_to_time_ago }}</small> </footer>
</div> <p>{{ comment.excerpt }}</p>
<p>{{ comment.excerpt }}</p> </article>
</li> {% endfor %}
{% endfor %}
</ul>
{% else %} {% else %}
<a href="{{ item_url }}#portfolio-item-comments"> <a href="{{ item_url }}#portfolio-item-comments">
<span class="fa fa-commenting-o fa-fw" aria-hidden="true"></span> <span class="fa fa-commenting-o fa-fw" aria-hidden="true"></span>
{{ 'AddNewComment'|get_lang }} {{ 'AddNewComment'|get_lang }}
</a> </a>
{% endif %} {% endif %}
</div> </section>
</div> </article>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

@ -19,20 +19,21 @@
<div class="{{ course ? 'col-md-9' : 'col-xs-12' }}"> <div class="{{ course ? 'col-md-9' : 'col-xs-12' }}">
{% if (categories) %} {% if (categories) %}
<div class="btn-group" data-toggle="buttons"> <div class="btn-toolbar">
{% for category in categories %} <div class="btn-group" data-toggle="buttons">
{% for category in categories %}
<label class="btn btn-default category-filters {{ category_id == category.id ? "active" : "" }}">
<input type="radio" value="{{ category.id }}" {{ category_id == category.id ? "checked" : "" }}>
{{ category.title }}
</label>
{% endfor %}
<label class="btn btn-default category-filters {{ category_id == category.id ? "active" : "" }}"> <label class="btn btn-default category-filters {{ category_id == category.id ? "active" : "" }}">
<input type="radio" value="{{ category.id }}" {{ category_id == category.id ? "checked" : "" }}> <input type="radio" value="0" {{ category_id == 0 ? "checked" : "" }}> {{ 'All'|get_lang }}
{{ category.title }}
</label> </label>
{% endfor %} </div>
<label class="btn btn-default category-filters {{ category_id == category.id ? "active" : "" }}">
<input type="radio" value="0" {{ category_id == 0 ? "checked" : "" }}> {{ 'All'|get_lang }}
</label>
</div> </div>
{% if (subcategories) %} {% if (subcategories) %}
<br /> <div class="btn-toolbar">
<div class="btn-block">
{% for subcategory in subcategories %} {% for subcategory in subcategories %}
<label class="btn btn-default"> <label class="btn btn-default">
{% set checked = '' %} {% set checked = '' %}

@ -1,46 +1,36 @@
<article class="thumbnail portfolio-item"> {% if item.isHighlighted %}
<div class="caption"> <span class="label label-success">
<div class="portfolio-actions pull-right"> <span class="fa fa-fw fa-star-o" aria-hidden="true"></span>
{% if _u.id == item.user.id %} {{ 'Highlighted'|get_lang }}
<a href="{{ baseurl ~ {'action':'edit_item', 'id':item.id}|url_encode }}"> </span>
{{ 'edit.png'|img(22, 'Edit'|get_lang) }} {% endif %}
</a>
{% if item.isVisible %} <article class="portfolio-item">
<a href="{{ baseurl ~ {'action':'hide_item', 'id':item.id}|url_encode }}"> <header>
{{ 'visible.png'|img(22, 'Invisible'|get_lang) }} <h4 class="h3">{{ item.title|remove_xss }}</h4>
</a>
{% else %}
<a href="{{ baseurl ~ {'action':'show_item', 'id':item.id}|url_encode }}">
{{ 'invisible.png'|img(22, 'Visible'|get_lang) }}
</a>
{% endif %}
<a href="{{ baseurl ~ {'action':'delete_item', 'id':item.id}|url_encode }}" class="btn-delete">
{{ 'delete.png'|img(22, 'Delete'|get_lang) }}
</a>
{% endif %}
{% if _u.id != item.user.id %}
<a href="{{ baseurl ~ {'action':'copy', 'copy':'item', 'id':item.id}|url_encode }}">
{{ 'copy.png'|img(22, 'CopyToMyPortfolio'|get_lang) }}
</a>
{% endif %}
{% if false|api_is_allowed_to_edit %} <ul class="fa-ul list-inline">
<a href="{{ baseurl ~ {'action':'teacher_copy', 'copy':'item', 'id':item.id}|url_encode }}"> <li>
{{ 'copy.png'|img(22, 'CopyToStudentPortfolio'|get_lang) }} <span class="fa-li fa fa-user" aria-hidden="true"></span>
</a> {{ item.user.completeName }}
</li>
<li>
<span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
{{ 'CreationDate'|get_lang ~ ': ' ~ item.creationDate|date_to_time_ago }}
</li>
{% if item.course and '1' == 'qualify_portfolio_item'|api_get_course_setting %} {% if last_edit %}
<a href="{{ baseurl ~ {'action':'qualify', 'item':item.id}|url_encode }}"> <li>
{{ 'quiz.png'|img(22, 'QualifyThisPortfolioItem'|get_lang) }} <span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
</a> {{ 'UpdatedDateXByUserY'|get_lang|format(last_edit.date|date_to_time_ago, last_edit.user) }}
{% endif %} </li>
{% elseif item.creationDate != item.updateDate %}
<li>
<span class="fa-li fa fa fa-clock-o" aria-hidden="true"></span>
{{ 'UpdatedDateX'|get_lang|format(item.updateDate|date_to_time_ago) }}
</li>
{% endif %} {% endif %}
</div>
<h4 class="h3">{{ item.title|remove_xss }}</h4>
<ul class="fa-ul list-inline">
{% if _c is empty %} {% if _c is empty %}
{% if item.session %} {% if item.session %}
<li> <li>
@ -53,52 +43,29 @@
{{ 'Course'|get_lang ~ ': ' ~ item.course.title }} {{ 'Course'|get_lang ~ ': ' ~ item.course.title }}
</li> </li>
{% endif %} {% endif %}
{% else %}
<li>
<span class="fa-li fa fa-user" aria-hidden="true"></span>
{{ item.user.completeName }}
</li>
{% endif %}
<li>
<span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
{{ 'CreationDate'|get_lang ~ ': ' ~ item.creationDate|date_to_time_ago }}
</li>
{% if _u.id == item.user.id and item.creationDate != item.updateDate %}
<li>
<span class="fa-li fa fa-clock-o" aria-hidden="true"></span>
{{ 'UpdateDate'|get_lang ~ ': ' ~ item.updateDate|date_to_time_ago }}
</li>
{% endif %} {% endif %}
<li>
<span class="fa-li fa fa-comment-o" aria-hidden="true"></span>
{{ 'XComments'|get_lang|format(item.comments.count) }}
</li>
</ul> </ul>
</header>
<hr> {{ item_content }}
{{ item_content|remove_xss }}
{% if attachment_list %}
<hr>
{% if attachment_list %}
<section>
{{ attachment_list }} {{ attachment_list }}
{% endif %} </section>
</div> {% endif %}
</article>
<hr> <section id="portfolio-item-comments">
<h1 class="h3">
<span class="fa fa-fw fa-comment-o" aria-hidden="true"></span>
{{ 'XComments'|get_lang|format(item.comments.count) }}
</h1>
<div id="portfolio-item-comments"> {{ comments }}
<h5 class="h4">{{ 'XComments'|get_lang|format(item.comments.count) }}</h5>
{{ comments }} {{ form }}
</section>
{{ form }} </article>
</div>
<script> <script>
$(function () { $(function () {

@ -0,0 +1,25 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\PluginBundle\XApi\ToolExperience\Statement\PortfolioItemViewed;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
class XApiPortfolioItemViewedHookObserver extends XApiActivityHookObserver implements HookPortfolioItemViewedObserverInterface
{
/**
* @throws OptimisticLockException
* @throws ORMException
*/
public function hookItemViewed(HookPortfolioItemViewedEventInterface $hookEvent)
{
/** @var Portfolio $item */
$item = $hookEvent->getEventData()['portfolio'];
$statement = (new PortfolioItemViewed($item))->generate();
$this->saveSharedStatement($statement);
}
}

@ -4,16 +4,10 @@
namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement; namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement;
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
use Chamilo\PluginBundle\XApi\ToolExperience\Activity\Course as CourseActivity; use Chamilo\PluginBundle\XApi\ToolExperience\Activity\Course as CourseActivity;
use Chamilo\PluginBundle\XApi\ToolExperience\Activity\Site as SiteActivity; use Chamilo\PluginBundle\XApi\ToolExperience\Activity\Site as SiteActivity;
use Chamilo\UserBundle\Entity\User;
use Xabbuh\XApi\Model\Attachment;
use Xabbuh\XApi\Model\Context; use Xabbuh\XApi\Model\Context;
use Xabbuh\XApi\Model\ContextActivities; use Xabbuh\XApi\Model\ContextActivities;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\LanguageMap;
use Xabbuh\XApi\Model\Statement; use Xabbuh\XApi\Model\Statement;
use Xabbuh\XApi\Model\StatementId; use Xabbuh\XApi\Model\StatementId;
use Xabbuh\XApi\Model\Uuid; use Xabbuh\XApi\Model\Uuid;
@ -56,55 +50,4 @@ abstract class BaseStatement
new ContextActivities(null, $groupingActivities) new ContextActivities(null, $groupingActivities)
); );
} }
/**
* @param array|PortfolioAttachment[] $portfolioAttachments
*
* @return array|Attachment[]
*/
protected function generateAttachments(array $portfolioAttachments, User $user): array
{
if (empty($portfolioAttachments)) {
return [];
}
$attachments = [];
$userDirectory = \UserManager::getUserPathById($user->getId(), 'system');
$attachmentsDirectory = $userDirectory.'portfolio_attachments/';
$langIso = api_get_language_isocode();
$cidreq = api_get_cidreq();
$baseUrl = api_get_path(WEB_CODE_PATH).'portfolio/index.php?'.($cidreq ? $cidreq.'&' : '');
foreach ($portfolioAttachments as $portfolioAttachment) {
$attachmentFilename = $attachmentsDirectory.$portfolioAttachment->getPath();
$display = LanguageMap::create(
['und' => $portfolioAttachment->getFilename()]
);
$description = null;
if ($portfolioAttachment->getComment()) {
$description = LanguageMap::create(
[$langIso => $portfolioAttachment->getComment()]
);
}
$attachments[] = new Attachment(
IRI::fromString('http://id.tincanapi.com/attachment/supporting_media'),
mime_content_type($attachmentFilename),
$portfolioAttachment->getSize(),
hash_file('sha256', $attachmentFilename),
$display,
$description,
IRL::fromString(
$baseUrl.http_build_query(['action' => 'download', 'file' => $portfolioAttachment->getPath()])
)
);
}
return $attachments;
}
} }

@ -0,0 +1,67 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement;
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
use Chamilo\UserBundle\Entity\User;
use UserManager;
use Xabbuh\XApi\Model\Attachment;
use Xabbuh\XApi\Model\IRI;
use Xabbuh\XApi\Model\IRL;
use Xabbuh\XApi\Model\LanguageMap;
trait PortfolioAttachmentsTrait
{
/**
* @param array<int, PortfolioAttachment> $portfolioAttachments
*
* @return array<int, Attachment>
*/
protected function generateAttachments(array $portfolioAttachments, User $user): array
{
if (empty($portfolioAttachments)) {
return [];
}
$attachments = [];
$userDirectory = UserManager::getUserPathById($user->getId(), 'system');
$attachmentsDirectory = $userDirectory.'portfolio_attachments/';
$langIso = api_get_language_isocode();
$cidreq = api_get_cidreq();
$baseUrl = api_get_path(WEB_CODE_PATH).'portfolio/index.php?'.($cidreq ? $cidreq.'&' : '');
foreach ($portfolioAttachments as $portfolioAttachment) {
$attachmentFilename = $attachmentsDirectory.$portfolioAttachment->getPath();
$display = LanguageMap::create(
['und' => $portfolioAttachment->getFilename()]
);
$description = null;
if ($portfolioAttachment->getComment()) {
$description = LanguageMap::create(
[$langIso => $portfolioAttachment->getComment()]
);
}
$attachments[] = new Attachment(
IRI::fromString('http://id.tincanapi.com/attachment/supporting_media'),
mime_content_type($attachmentFilename),
$portfolioAttachment->getSize(),
hash_file('sha256', $attachmentFilename),
$display,
$description,
IRL::fromString(
$baseUrl.http_build_query(['action' => 'download', 'file' => $portfolioAttachment->getPath()])
)
);
}
return $attachments;
}
}

@ -0,0 +1,41 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement;
use Chamilo\CoreBundle\Entity\Portfolio;
use Chamilo\PluginBundle\XApi\ToolExperience\Activity\PortfolioCategory;
use Xabbuh\XApi\Model\Context;
abstract class PortfolioItem extends BaseStatement
{
protected $item;
public function __construct(Portfolio $item)
{
$this->item = $item;
}
protected function generateContext(): Context
{
$context = parent::generateContext();
$category = $this->item->getCategory();
if ($category) {
$categoryActivity = new PortfolioCategory($category);
$contextActivities = $context
->getContextActivities()
->withAddedCategoryActivity(
$categoryActivity->generate()
)
;
$context = $context->withContextActivities($contextActivities);
}
return $context;
}
}

@ -16,6 +16,8 @@ use Xabbuh\XApi\Model\Statement;
class PortfolioItemCommented extends BaseStatement class PortfolioItemCommented extends BaseStatement
{ {
use PortfolioAttachmentsTrait;
/** /**
* @var \Chamilo\CoreBundle\Entity\PortfolioComment * @var \Chamilo\CoreBundle\Entity\PortfolioComment
*/ */

@ -4,12 +4,12 @@
namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement; namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement;
use Chamilo\CoreBundle\Entity\Portfolio as PortfolioEntity;
use Chamilo\CoreBundle\Entity\PortfolioAttachment; use Chamilo\CoreBundle\Entity\PortfolioAttachment;
use Chamilo\PluginBundle\XApi\ToolExperience\Activity\PortfolioCategory as PortfolioCategoryActivity; use Chamilo\PluginBundle\XApi\ToolExperience\Activity\PortfolioItem;
use Chamilo\PluginBundle\XApi\ToolExperience\Activity\PortfolioItem as PortfolioItemActivity; use Chamilo\PluginBundle\XApi\ToolExperience\Actor\User;
use Chamilo\PluginBundle\XApi\ToolExperience\Actor\User as UserActor; use Chamilo\PluginBundle\XApi\ToolExperience\Statement\PortfolioItem as PortfolioItemStatement;
use Chamilo\PluginBundle\XApi\ToolExperience\Verb\Shared as SharedVerb; use Chamilo\PluginBundle\XApi\ToolExperience\Verb\Shared;
use Database;
use Xabbuh\XApi\Model\Statement; use Xabbuh\XApi\Model\Statement;
/** /**
@ -17,47 +17,26 @@ use Xabbuh\XApi\Model\Statement;
* *
* @package Chamilo\PluginBundle\XApi\ToolExperience\Statement * @package Chamilo\PluginBundle\XApi\ToolExperience\Statement
*/ */
class PortfolioItemShared extends BaseStatement class PortfolioItemShared extends PortfolioItemStatement
{ {
/** use PortfolioAttachmentsTrait;
* @var \Chamilo\CoreBundle\Entity\Portfolio
*/
private $portfolioItem;
public function __construct(PortfolioEntity $item)
{
$this->portfolioItem = $item;
}
public function generate(): Statement public function generate(): Statement
{ {
$userActor = new UserActor( $itemAuthor = $this->item->getUser();
$this->portfolioItem->getUser()
);
$sharedVerb = new SharedVerb();
$itemActivity = new PortfolioItemActivity($this->portfolioItem);
$context = $this->generateContext(); $userActor = new User($itemAuthor);
$sharedVerb = new Shared();
$itemActivity = new PortfolioItem($this->item);
if ($this->portfolioItem->getCategory()) { $context = $this->generateContext();
$categoryActivity = new PortfolioCategoryActivity($this->portfolioItem->getCategory());
$contextActivities = $context
->getContextActivities()
->withAddedCategoryActivity(
$categoryActivity->generate()
);
$context = $context->withContextActivities($contextActivities);
}
$em = \Database::getManager(); $itemAttachments = Database::getManager()
$itemAttachments = $em->getRepository(PortfolioAttachment::class)->findFromItem($this->portfolioItem); ->getRepository(PortfolioAttachment::class)
->findFromItem($this->item)
;
$attachments = $this->generateAttachments( $attachments = $this->generateAttachments($itemAttachments, $itemAuthor);
$itemAttachments,
$this->portfolioItem->getUser()
);
return new Statement( return new Statement(
$this->generateStatementId('portfolio-item'), $this->generateStatementId('portfolio-item'),
@ -66,7 +45,7 @@ class PortfolioItemShared extends BaseStatement
$itemActivity->generate(), $itemActivity->generate(),
null, null,
null, null,
$this->portfolioItem->getCreationDate(), $this->item->getCreationDate(),
null, null,
$context, $context,
$attachments $attachments

@ -0,0 +1,48 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\XApi\ToolExperience\Statement;
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
use Chamilo\PluginBundle\XApi\ToolExperience\Activity\PortfolioItem;
use Chamilo\PluginBundle\XApi\ToolExperience\Actor\User;
use Chamilo\PluginBundle\XApi\ToolExperience\Statement\PortfolioItem as PortfolioItemStatement;
use Chamilo\PluginBundle\XApi\ToolExperience\Verb\Viewed;
use Database;
use Xabbuh\XApi\Model\Statement;
class PortfolioItemViewed extends PortfolioItemStatement
{
use PortfolioAttachmentsTrait;
public function generate(): Statement
{
$user = api_get_user_entity(api_get_user_id());
$itemAuthor = $this->item->getUser();
$itemAttachments = Database::getManager()
->getRepository(PortfolioAttachment::class)
->findFromItem($this->item)
;
$actor = new User($user);
$verb = new Viewed();
$object = new PortfolioItem($this->item);
$context = $this->generateContext();
$attachments = $this->generateAttachments($itemAttachments, $itemAuthor);
return new Statement(
$this->generateStatementId('portfolio-item'),
$actor->generate(),
$verb->generate(),
$object->generate(),
null,
null,
$this->item->getCreationDate(),
null,
$context,
$attachments
);
}
}

@ -220,6 +220,7 @@ class XApiPlugin extends Plugin implements HookPluginInterface
$quizEndHook = XApiQuizEndHookObserver::create(); $quizEndHook = XApiQuizEndHookObserver::create();
$portfolioItemAddedHook = XApiPortfolioItemAddedHookObserver::create(); $portfolioItemAddedHook = XApiPortfolioItemAddedHookObserver::create();
$portfolioItemCommentedHook = XApiPortfolioItemCommentedHookObserver::create(); $portfolioItemCommentedHook = XApiPortfolioItemCommentedHookObserver::create();
$portfolioItemViewedHook = XApiPortfolioItemViewedHookObserver::create();
$learningPathItemViewedEvent = HookLearningPathItemViewed::create(); $learningPathItemViewedEvent = HookLearningPathItemViewed::create();
$learningPathEndEvent = HookLearningPathEnd::create(); $learningPathEndEvent = HookLearningPathEnd::create();
@ -227,6 +228,7 @@ class XApiPlugin extends Plugin implements HookPluginInterface
$quizEndEvent = HookQuizEnd::create(); $quizEndEvent = HookQuizEnd::create();
$portfolioItemAddedEvent = HookPortfolioItemAdded::create(); $portfolioItemAddedEvent = HookPortfolioItemAdded::create();
$portfolioItemCommentedEvent = HookPortfolioItemCommented::create(); $portfolioItemCommentedEvent = HookPortfolioItemCommented::create();
$portfolioItemViewedEvent = HookPortfolioItemViewed::create();
if ('true' === $this->get(self::SETTING_LRS_LP_ITEM_ACTIVE)) { if ('true' === $this->get(self::SETTING_LRS_LP_ITEM_ACTIVE)) {
$learningPathItemViewedEvent->attach($learningPathItemViewedHook); $learningPathItemViewedEvent->attach($learningPathItemViewedHook);
@ -255,9 +257,11 @@ class XApiPlugin extends Plugin implements HookPluginInterface
if ('true' === $this->get(self::SETTING_LRS_PORTFOLIO_ACTIVE)) { if ('true' === $this->get(self::SETTING_LRS_PORTFOLIO_ACTIVE)) {
$portfolioItemAddedEvent->attach($portfolioItemAddedHook); $portfolioItemAddedEvent->attach($portfolioItemAddedHook);
$portfolioItemCommentedEvent->attach($portfolioItemCommentedHook); $portfolioItemCommentedEvent->attach($portfolioItemCommentedHook);
$portfolioItemViewedEvent->attach($portfolioItemViewedHook);
} else { } else {
$portfolioItemAddedEvent->detach($portfolioItemAddedHook); $portfolioItemAddedEvent->detach($portfolioItemAddedHook);
$portfolioItemCommentedEvent->attach($portfolioItemCommentedHook); $portfolioItemCommentedEvent->detach($portfolioItemCommentedHook);
$portfolioItemViewedEvent->detach($portfolioItemViewedHook);
} }
return $this; return $this;

@ -31,6 +31,10 @@ class Portfolio
public const TYPE_ITEM = 1; public const TYPE_ITEM = 1;
public const TYPE_COMMENT = 2; public const TYPE_COMMENT = 2;
public const VISIBILITY_HIDDEN = 0;
public const VISIBILITY_VISIBLE = 1;
public const VISIBILITY_HIDDEN_EXCEPT_TEACHER = 2;
/** /**
* @var int * @var int
* *
@ -94,9 +98,9 @@ class Portfolio
/** /**
* @var bool * @var bool
* *
* @ORM\Column(name="is_visible", type="boolean", options={"default": true}) * @ORM\Column(name="visibility", type="smallint", options={"default": 1})
*/ */
protected $isVisible = true; protected $visibility = 1;
/** /**
* @var \Chamilo\CoreBundle\Entity\PortfolioCategory * @var \Chamilo\CoreBundle\Entity\PortfolioCategory
@ -133,6 +137,20 @@ class Portfolio
*/ */
private $score; private $score;
/**
* @var bool
*
* @ORM\Column(name="is_highlighted", type="boolean", options={"default": false})
*/
private $isHighlighted = false;
/**
* @var bool
*
* @ORM\Column(name="is_template", type="boolean", options={"default": false})
*/
private $isTemplate = false;
/** /**
* Portfolio constructor. * Portfolio constructor.
*/ */
@ -224,11 +242,13 @@ class Portfolio
/** /**
* Get title. * Get title.
*
* @return string
*/ */
public function getTitle() public function getTitle(bool $stripTags = false): string
{ {
if ($stripTags) {
return strip_tags($this->title);
}
return $this->title; return $this->title;
} }
@ -312,26 +332,20 @@ class Portfolio
/** /**
* Set isVisible. * Set isVisible.
*
* @param bool $isVisible
*
* @return Portfolio
*/ */
public function setIsVisible($isVisible) public function setVisibility(int $visibility): Portfolio
{ {
$this->isVisible = $isVisible; $this->visibility = $visibility;
return $this; return $this;
} }
/** /**
* Get isVisible. * Get isVisible.
*
* @return bool
*/ */
public function isVisible() public function getVisibility(): int
{ {
return $this->isVisible; return $this->visibility;
} }
/** /**
@ -415,4 +429,28 @@ class Portfolio
{ {
$this->score = $score; $this->score = $score;
} }
public function isHighlighted(): bool
{
return $this->isHighlighted;
}
public function setIsHighlighted(bool $isHighlighted): Portfolio
{
$this->isHighlighted = $isHighlighted;
return $this;
}
public function isTemplate(): bool
{
return $this->isTemplate;
}
public function setIsTemplate(bool $isTemplate): Portfolio
{
$this->isTemplate = $isTemplate;
return $this;
}
} }

@ -243,7 +243,7 @@ class PortfolioCategory
if ($onlyVisibles) { if ($onlyVisibles) {
$criteria->andWhere( $criteria->andWhere(
Criteria::expr()->eq('isVisible', true) Criteria::expr()->eq('visibility', Portfolio::VISIBILITY_VISIBLE)
); );
} }

@ -114,6 +114,13 @@ class PortfolioComment
*/ */
private $score; private $score;
/**
* @var bool
*
* @ORM\Column(name="is_template", type="boolean", options={"default": false})
*/
private $isTemplate = false;
/** /**
* PortfolioComment constructor. * PortfolioComment constructor.
*/ */
@ -249,4 +256,16 @@ class PortfolioComment
{ {
return $this->lvl; return $this->lvl;
} }
public function isTemplate(): bool
{
return $this->isTemplate;
}
public function setIsTemplate(bool $isTemplate): PortfolioComment
{
$this->isTemplate = $isTemplate;
return $this;
}
} }

@ -16,7 +16,7 @@ use Doctrine\ORM\EntityRepository;
*/ */
class PortfolioRepository extends EntityRepository class PortfolioRepository extends EntityRepository
{ {
public function findItemsByUser(User $user, ?Course $course, ?Session $session, ?array $orderBy = null): array public function findItemsByUser(User $user, ?Course $course, ?Session $session, ?array $orderBy = null, array $visibility = []): array
{ {
$criteria = []; $criteria = [];
$criteria['user'] = $user; $criteria['user'] = $user;
@ -26,6 +26,10 @@ class PortfolioRepository extends EntityRepository
$criteria['session'] = $session; $criteria['session'] = $session;
} }
if ($visibility) {
$criteria['visibility'] = $visibility;
}
return $this->findBy($criteria, $orderBy); return $this->findBy($criteria, $orderBy);
} }
} }

@ -0,0 +1,31 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
/**
* @method array findById(int|array $ids)
*/
class TagRepository extends EntityRepository
{
public function findByFieldIdAndText(int $field, string $text, int $limit = 0)
{
$qb = $this
->createQueryBuilder('t')
->where("t.tag LIKE :text")
->andWhere('t.fieldId = :field');
if ($limit > 0) {
$qb->setMaxResults($limit);
}
return $qb
->setParameter('field', $field)
->setParameter('text', "$text%")
->getQuery()
->getResult();
}
}

@ -9,7 +9,7 @@ use Doctrine\ORM\Mapping as ORM;
* Tag. * Tag.
* *
* @ORM\Table(name="tag") * @ORM\Table(name="tag")
* @ORM\Entity * @ORM\Entity(repositoryClass="Chamilo\CoreBundle\Entity\Repository\TagRepository")
*/ */
class Tag class Tag
{ {

Loading…
Cancel
Save