From 9b9291b9b261d05cbc3c78fc7301af7ca20d66c0 Mon Sep 17 00:00:00 2001 From: oveleon Date: Fri, 8 Mar 2019 16:40:16 +0100 Subject: [PATCH] Initial commit: Frontend module member avatar added --- composer.json | 44 ++++ src/ContaoManager/Plugin.php | 33 +++ src/ContaoMemberExtensionBundle.php | 17 ++ src/Resources/contao/classes/Member.php | 202 ++++++++++++++++++ src/Resources/contao/config/config.php | 23 ++ src/Resources/contao/dca/tl_member.php | 35 +++ .../contao/dca/tl_member_settings.php | 35 +++ src/Resources/contao/dca/tl_module.php | 12 ++ src/Resources/contao/languages/de/modules.xlf | 14 ++ .../contao/languages/de/tl_member.xlf | 22 ++ .../languages/de/tl_member_settings.xlf | 18 ++ src/Resources/contao/modules/ModuleAvatar.php | 107 ++++++++++ .../templates/member/member_avatar.html5 | 9 + src/Resources/public/avatar.png | Bin 0 -> 4010 bytes 14 files changed, 571 insertions(+) create mode 100644 composer.json create mode 100644 src/ContaoManager/Plugin.php create mode 100644 src/ContaoMemberExtensionBundle.php create mode 100644 src/Resources/contao/classes/Member.php create mode 100644 src/Resources/contao/config/config.php create mode 100644 src/Resources/contao/dca/tl_member.php create mode 100644 src/Resources/contao/dca/tl_member_settings.php create mode 100644 src/Resources/contao/dca/tl_module.php create mode 100644 src/Resources/contao/languages/de/modules.xlf create mode 100644 src/Resources/contao/languages/de/tl_member.xlf create mode 100644 src/Resources/contao/languages/de/tl_member_settings.xlf create mode 100644 src/Resources/contao/modules/ModuleAvatar.php create mode 100644 src/Resources/contao/templates/member/member_avatar.html5 create mode 100644 src/Resources/public/avatar.png diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..25745c2 --- /dev/null +++ b/composer.json @@ -0,0 +1,44 @@ +{ + "name":"oveleon/contao-member-extension-bundle", + "type":"contao-bundle", + "description":"Member feature extension for Contao.", + "keywords":["contao","member-extension-bundle"], + "homepage":"https://oveleon.de/", + "license":"MIT", + "authors":[ + { + "name":"Oveleon", + "homepage":"https://oveleon.de/", + "role":"Developer" + } + ], + "require":{ + "php":">=7.1", + "contao/core-bundle":"^4.4" + }, + "require-dev": { + "contao/manager-plugin": "^2.0" + }, + "conflict": { + "contao/core": "*", + "contao/core-bundle": "4.4.1", + "contao/manager-plugin": "<2.0 || >=3.0" + }, + "autoload":{ + "psr-4": { + "Oveleon\\ContaoMemberExtensionBundle\\": "src/" + }, + "classmap": [ + "src/Resources/contao/" + ], + "exclude-from-classmap": [ + "src/Resources/contao/config/", + "src/Resources/contao/dca/", + "src/Resources/contao/languages/", + "src/Resources/contao/templates/" + ] + }, + "extra":{ + "contao-manager-plugin": "Oveleon\\ContaoMemberExtensionBundle\\ContaoManager\\Plugin" + } +} \ No newline at end of file diff --git a/src/ContaoManager/Plugin.php b/src/ContaoManager/Plugin.php new file mode 100644 index 0000000..b97bb33 --- /dev/null +++ b/src/ContaoManager/Plugin.php @@ -0,0 +1,33 @@ +setLoadAfter([ContaoCoreBundle::class]) + ->setReplace(['contao-member-extension-bundle']), + ]; + } +} diff --git a/src/ContaoMemberExtensionBundle.php b/src/ContaoMemberExtensionBundle.php new file mode 100644 index 0000000..1a1bde1 --- /dev/null +++ b/src/ContaoMemberExtensionBundle.php @@ -0,0 +1,17 @@ + + */ +class Member extends \Frontend +{ + /** + * Update avatar of member + * + * @param \FrontendUser $objUser + * @param array $arrData + */ + public function updateAvatar($objUser, $arrData) + { + $objMember = \MemberModel::findByPk($objUser->id); + + if ($objMember === null) + { + return; + } + + $file = $_SESSION['FILES']['avatar']; + $maxlength_kb = $this->getMaximumUploadSize(); + + // Sanitize the filename + try + { + $file['name'] = \StringUtil::sanitizeFileName($file['name']); + } + catch (\InvalidArgumentException $e) + { + // ToDo: Fehler: Dateiname beinhaltet unzulässige Zeichen + + return; + } + + // Invalid file name + if (!\Validator::isValidFileName($file['name'])) + { + // ToDo: Fehler: Dateiname beinhaltet unzulässige Zeichen + + return; + } + + // File was not uploaded + // ToDo + + // File is too big + if ($file['size'] > $maxlength_kb) + { + // ToDo: Fehler: Datei zu groß + unset($_SESSION['FILES']['avatar']); + + return; + } + + $objFile = new \File($file['name']); + $uploadTypes = \StringUtil::trimsplit(',', \Config::get('validImageTypes')); + + // File type is not allowed + if (!\in_array($objFile->extension, $uploadTypes)) + { + // ToDo: Fehler: Dateityp nicht erlaubt + unset($_SESSION['FILES']['avatar']); + + return; + } + + if ($arrImageSize = @getimagesize($file['tmp_name'])) + { + $intImageWidth = \Config::get('imageWidth'); + + // Image exceeds maximum image width + if ($intImageWidth > 0 && $arrImageSize[0] > $intImageWidth) + { + // ToDo: Fehler: Bild ist zu groß in der breite + unset($_SESSION['FILES']['avatar']); + + return; + } + + $intImageHeight = \Config::get('imageHeight'); + + // Image exceeds maximum image height + if ($intImageHeight > 0 && $arrImageSize[1] > $intImageHeight) + { + // ToDo: Fehler: Bild ist zu groß in der höhe + unset($_SESSION['FILES']['avatar']); + + return; + } + + $_SESSION['FILES']['avatar'] = $_SESSION['FILES']['avatar']; + + // Overwrite the upload folder with user's home directory + if ($objMember->assignDir && $objMember->homeDir) + { + $intUploadFolder = $objMember->homeDir; + } + + $objUploadFolder = \FilesModel::findByUuid($intUploadFolder); + + // The upload folder could not be found + if ($objUploadFolder === null) + { + throw new \Exception("Invalid upload folder ID $intUploadFolder"); + } + + $strUploadFolder = $objUploadFolder->path; + + // Store the file if the upload folder exists + if ($strUploadFolder != '' && is_dir(TL_ROOT . '/' . $strUploadFolder)) + { + $this->import('Files'); + + // Move the file to its destination + $this->Files->move_uploaded_file($file['tmp_name'], $strUploadFolder . '/' . $file['name']); + $this->Files->chmod($strUploadFolder . '/' . $file['name'], \Config::get('defaultFileChmod')); + + $strUuid = null; + $strFile = $strUploadFolder . '/' . $file['name']; + + // Generate the DB entries + if (\Dbafs::shouldBeSynchronized($strFile)) + { + $objModel = \FilesModel::findByPath($strFile); + + if ($objModel === null) + { + $objModel = \Dbafs::addResource($strFile); + } + + $strUuid = \StringUtil::binToUuid($objModel->uuid); + + // Update the hash of the target folder + \Dbafs::updateFolderHashes($strUploadFolder); + + // Update member avatar + $objMember->avatar = $objModel->uuid; + $objMember->save(); + } + + // Add the session entry (see #6986) + $_SESSION['FILES']['avatar'] = array + ( + 'name' => $file['name'], + 'type' => $file['type'], + 'tmp_name' => TL_ROOT . '/' . $strFile, + 'error' => $file['error'], + 'size' => $file['size'], + 'uploaded' => true, + 'uuid' => $strUuid + ); + + // Add a log entry + $this->log('File "' . $strUploadFolder . '/' . $file['name'] . '" has been uploaded', __METHOD__, TL_FILES); + } + } + + unset($_SESSION['FILES']['avatar']); + } + + /** + * Return the maximum upload file size in bytes + * + * @return string + */ + protected function getMaximumUploadSize() + { + // Get the upload_max_filesize from the php.ini + $upload_max_filesize = ini_get('upload_max_filesize'); + + // Convert the value to bytes + if (stripos($upload_max_filesize, 'K') !== false) + { + $upload_max_filesize = round($upload_max_filesize * 1024); + } + elseif (stripos($upload_max_filesize, 'M') !== false) + { + $upload_max_filesize = round($upload_max_filesize * 1024 * 1024); + } + elseif (stripos($upload_max_filesize, 'G') !== false) + { + $upload_max_filesize = round($upload_max_filesize * 1024 * 1024 * 1024); + } + + return min($upload_max_filesize, \Config::get('maxFileSize')); + } +} \ No newline at end of file diff --git a/src/Resources/contao/config/config.php b/src/Resources/contao/config/config.php new file mode 100644 index 0000000..88aa8b9 --- /dev/null +++ b/src/Resources/contao/config/config.php @@ -0,0 +1,23 @@ + array('tl_member_settings'), + 'hideInNavigation' => true, +); + +// Front end modules +array_insert($GLOBALS['FE_MOD']['user'], -1, array +( + 'avatar' => '\\Oveleon\\ContaoMemberExtensionBundle\\ModuleAvatar' +)); + +// Register hooks +$GLOBALS['TL_HOOKS']['updatePersonalData'][] = array('\\Oveleon\\ContaoMemberExtensionBundle\\Member', 'updateAvatar'); diff --git a/src/Resources/contao/dca/tl_member.php b/src/Resources/contao/dca/tl_member.php new file mode 100644 index 0000000..4c06fd5 --- /dev/null +++ b/src/Resources/contao/dca/tl_member.php @@ -0,0 +1,35 @@ +addField(array('avatar'), 'personal_legend', Contao\CoreBundle\DataContainer\PaletteManipulator::POSITION_APPEND) + ->applyToPalette('default', 'tl_member') +; + +// Add global operations +array_insert($GLOBALS['TL_DCA']['tl_member']['list']['global_operations'], 0, array +( + 'settings' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_member']['settings'], + 'href' => 'do=member_settings', + 'icon' => 'edit.svg', + 'attributes' => 'onclick="Backend.getScrollOffset()" accesskey="e"' + ) +)); + +// Add fields to tl_user +$GLOBALS['TL_DCA']['tl_member']['fields']['avatar'] = array +( + 'label' => &$GLOBALS['TL_LANG']['tl_member']['avatar'], + 'exclude' => true, + 'inputType' => 'fileTree', + 'eval' => array('feEditable'=>true, 'feViewable'=>true, 'feGroup'=>'personal', 'fieldType'=>'radio', 'filesOnly'=>true, 'isGallery'=>true, 'extensions'=>Config::get('validImageTypes'), 'tl_class'=>'clr'), + 'sql' => "binary(16) NULL" +); \ No newline at end of file diff --git a/src/Resources/contao/dca/tl_member_settings.php b/src/Resources/contao/dca/tl_member_settings.php new file mode 100644 index 0000000..8ab9597 --- /dev/null +++ b/src/Resources/contao/dca/tl_member_settings.php @@ -0,0 +1,35 @@ + array + ( + 'dataContainer' => 'File', + 'closed' => true + ), + + // Palettes + 'palettes' => array + ( + 'default' => '{avatar_legend},defaultAvatar;' + ), + + // Fields + 'fields' => array + ( + 'defaultAvatar' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_member_settings']['defaultAvatar'], + 'inputType' => 'fileTree', + 'eval' => array('fieldType'=>'radio', 'filesOnly'=>true, 'isGallery'=>true, 'extensions'=>Config::get('validImageTypes'), 'tl_class'=>'clr') + ) + ) +); diff --git a/src/Resources/contao/dca/tl_module.php b/src/Resources/contao/dca/tl_module.php new file mode 100644 index 0000000..228ce4f --- /dev/null +++ b/src/Resources/contao/dca/tl_module.php @@ -0,0 +1,12 @@ + '{title_legend},name,headline,type;{source_legend},imgSize;{template_legend:hide},memberTpl;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID' +)); diff --git a/src/Resources/contao/languages/de/modules.xlf b/src/Resources/contao/languages/de/modules.xlf new file mode 100644 index 0000000..f2f1ca3 --- /dev/null +++ b/src/Resources/contao/languages/de/modules.xlf @@ -0,0 +1,14 @@ + + + + + Avatar + Avatar + + + Displays the avatar of the member. + Zeigt den Avatar des Mitgliedes an. + + + + \ No newline at end of file diff --git a/src/Resources/contao/languages/de/tl_member.xlf b/src/Resources/contao/languages/de/tl_member.xlf new file mode 100644 index 0000000..9a84a2d --- /dev/null +++ b/src/Resources/contao/languages/de/tl_member.xlf @@ -0,0 +1,22 @@ + + + + + Avatar + Avatar + + + Here you can choose an avatar for the member. + Hier können Sie einen Avatar für das Mitglied auswählen. + + + Settings + Einstellungen + + + Member settings + Mitglieder-Einstellungen + + + + \ No newline at end of file diff --git a/src/Resources/contao/languages/de/tl_member_settings.xlf b/src/Resources/contao/languages/de/tl_member_settings.xlf new file mode 100644 index 0000000..6a88ea9 --- /dev/null +++ b/src/Resources/contao/languages/de/tl_member_settings.xlf @@ -0,0 +1,18 @@ + + + + + Default avatar + Standard-Avatar + + + The default avatar is displayed for members who have not set their own avatar. + Der Standard-Avatar wird bei Mitgliedern angezeigt, die keinen eigene Avatar gesetzt haben. + # + + Avatar + Avatar + + + + \ No newline at end of file diff --git a/src/Resources/contao/modules/ModuleAvatar.php b/src/Resources/contao/modules/ModuleAvatar.php new file mode 100644 index 0000000..b374910 --- /dev/null +++ b/src/Resources/contao/modules/ModuleAvatar.php @@ -0,0 +1,107 @@ + + */ +class ModuleAvatar extends \Module +{ + + /** + * Template + * @var string + */ + protected $strTemplate = 'member_avatar'; + + /** + * Return a wildcard in the back end + * + * @return string + */ + public function generate() + { + if (TL_MODE == 'BE') + { + /** @var BackendTemplate|object $objTemplate */ + $objTemplate = new \BackendTemplate('be_wildcard'); + + $objTemplate->wildcard = '### ' . Utf8::strtoupper($GLOBALS['TL_LANG']['FMD']['avatar'][0]) . ' ###'; + $objTemplate->title = $this->headline; + $objTemplate->id = $this->id; + $objTemplate->link = $this->name; + $objTemplate->href = 'contao/main.php?do=themes&table=tl_module&act=edit&id=' . $this->id; + + return $objTemplate->parse(); + } + + // Return if user is not logged in + if (!FE_USER_LOGGED_IN) + { + return ''; + } + + if ($this->memberTpl != '') + { + $this->strTemplate = $this->memberTpl; + } + + return parent::generate(); + } + + /** + * Generate the module + */ + protected function compile() + { + $this->size = $this->imgSize; + + $this->import('FrontendUser', 'User'); + + if ($this->User->avatar == '' && \Config::get('defaultAvatar') == '') + { + return ''; + } + + if ($this->User->avatar == '') + { + $objFile = \FilesModel::findByUuid(\Config::get('defaultAvatar')); + + if ($objFile === null || !is_file(TL_ROOT . '/' . $objFile->path)) + { + return ''; + } + + $this->singleSRC = $objFile->path; + + $this->addImageToTemplate($this->Template, $this->arrData); + return; + } + + $objFile = \FilesModel::findByUuid($this->User->avatar); + + if ($objFile === null || !is_file(TL_ROOT . '/' . $objFile->path)) + { + $this->singleSRC = \FilesModel::findByUuid(\Config::get('defaultAvatar'))->path; + + $this->addImageToTemplate($this->Template, $this->arrData); + return; + } + + $this->singleSRC = $objFile->path; + + $this->addImageToTemplate($this->Template, $this->arrData, null, null, $objFile); + } +} diff --git a/src/Resources/contao/templates/member/member_avatar.html5 b/src/Resources/contao/templates/member/member_avatar.html5 new file mode 100644 index 0000000..3fbd2e6 --- /dev/null +++ b/src/Resources/contao/templates/member/member_avatar.html5 @@ -0,0 +1,9 @@ +extend('block_searchable'); ?> + +block('content'); ?> + +
+ insert('picture_default', $this->picture); ?> +
+ +endblock(); ?> diff --git a/src/Resources/public/avatar.png b/src/Resources/public/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..b89587ad2d33d52880803ccce44eaa026c370fc4 GIT binary patch literal 4010 zcmcJSc{tQv-@tz}W0##NM0Z7EMkpk1mC2f=xEa5)^fPWd^^~m?W^PL{H=#^fO12V1 zKa?e5jHZNc8O_*<3}(g>MYi%x?;r2~@B3WOb)9p4&-t9~d!6rcu9I@s$wo>-MFIc- zDLY$B7XUzlzp5BYKvEh47X+g0tb?o7;^MdeE!*4MpFT}ZPEK>V3v+Wz91eG6l#>C@-=`K5)0<Xm0My?CkvL=%<+({@~!q$jCUK|7B=sbb6XMHa0mhFv4aJ z&CJXRl8+9i5<`J?1RXSvbPQ=?@@>!U9B>y>0@4G}X@1 z!u6{69MeXct`AsQh?*qNf5a_vejfddv=zHMH*H`0qhjYjZ$6@ZjBG$Lj2M}#OU8lS zI8aQ|XD{~DFF}H3(R8GsmncmC#S@bR;D3e%90&e}6@Zif{fQgX&eA=><4>xe=Cw!t z=yV&2%o}cvrq`@0eq90M$22)t^;R^g1+NC1YFA!oY7pt+2{bCXo@DKSNcZQ zDlcBAnKqMgWU43?Ju`FNx$}VyEjZ|S>n;B-c`z)rMRvYi26|E1=iO4&CJu%Ln`b}c z#Y$4^6it`2W1WbsRHvjHmsMq`Q~VVU-{UUr^sB}@)_jELkWx z>mXLM=8ZbIdY!8AazhWsW9P9f3hiF;f<-?pI3M z<&Ox_K7b)9MQc?Lj-fEqTNS(JrNO^d42Mvd$t_%W6cYc6L#U5!f|e2(drm>`8COIh z#*nwe(I7c2dw)1{54=Gzn;|Lr+EkCJ!2?UBy^5gRw4}kfy*;$V`zKiqN@K(+!9^Go zyGQ$B0^eJ4@|+m;j9noL-q&Sjw|6Iz5syndPE>N2$+A|1s$q*_1YE?ka?#e1As5%5 zvyq@?AAB9DL{m8kKv}}n-`=?bn4-EYCmX1`;B2ux_^K)7E{ZHL(uO3sAq)!4|`gVwW7lC;gmE{x;1-<_H zOdj;F!_~H{gEx{E1pK%O!*)L;wO&*#2d8H_D8Yu4dH+a*Dwki*qhLZg;Y*bqyfLS< z71f4Fb`A^lhYUIc2*FnaRSYiPN0NQ3 z4Tliq$haHK36H`YJT2JR?zt3f(s8CZ0J{vk&IWX*$Q`U+=&g6AiA4vL!vV4Uv=pj4(^rx7D36%66rp|W;SK?Kaj z-q**XGi#dFL387ZArgb}VRxD^HLC(eEMP|L5sH|~%0s+U2FYKL<* z;fLU)G&cKC{+}TtXy2>J^R)9Rl}K2TV8tyJ*xhp^0-$8ZV68;Z-}xPCFx8NAW-r`O zNk`f5MF&U)LrX6{bl$^3ORI6h=sl6tS4}8#ee?{;?}^N+T(A(87N-Y2H4OZo9s?Qd zlcKrYYTsQx^)ooZo<`w~;_%vq4$(kJ_^q)76cj-z3rB#)sx^hOppM(z zCQ}Ry_Z~GuQuNM8#mK?ng8NlyuyW>nWP=%Oa&p}Rpm5Tojw>L^d$rr0A;+~gB;~Y> zypI(?Sy5LjC?J(OALxDYomNv2pFNPP4FKr8qPIqHRv^~d?He)MdLnD9KLA?N-*N>2 zEdCf42{Yfh%iFiBfZ{BSM*71LNR!3BoTmdDnlAmi;f2yALH-wyy21=K6LzOfnuoYO zQHFWKHZM_a~OK)+o3|I?&K{3ombZ-*w~a_5vmyXmjE+%Quid`#?I>H0=WFOt3L zH94Chs|!VL%Ae^w3bmasfLm=BCV& zSe~^lBPI`T(U!lGO1xTy@TeXdj(I6l6pHn`6dPo>Fe1#}skH1K^v7bfI=E5k?lgq2 zzJ{O#|9pNosxA*S zL$#yF$|^vNTEJT)mj_m_$JY^!I(61p%GN5r(Keh~JiVr(_xJbo(>6rQ!|EcIZ#o;a zq!U-7w|y3+qvewe^>=JOnNO|}M)*qjn!MfZwV$Bdv~BiP#i7L9{Kr_x)vUgJqmX9w z_Du<{^IiLUM%dP^O!i2-V$AbRmhlPDDt`Fy@7}J3d^_pms$lR5QH`YTke)r1g3zn3 zWwi;uJGvZukN+&ZSbU%EBD#Y%dvAKM?MjabF!`jCAY5@EymnU(^D>F02M}HQn*)HZ zN&}SgD;X;(wq$SXd|`PSlDyrwKd?|fiE~>8usymePS+8N#h0D45vE#sGVvzotcn$2g9kJt3|VDdCE~B!|~KfPz&hL8Wl;<1b%xGHGC324Rlnz z5hj&>yL3IePSHqK7+NDrTyEnaB?JCNmiYU8QR1h~)|{PVATx`b|NQ7zN7i?d_#l&J zahgfUI&7`#7uD0gEla^<#M0`;_$Ps;*&Bho*$N2ow)Y|I9x(1gZt{v7&{V#tk{qc^ z9F6QfME&dyP>NL(Ie|PUkFfKJoD;ZKvawI|)?NU$<^Zl5o@L*bqQ_u>R#Ab_1;+Mp z=r!Wxnu^kP6^u2w7AI4`>g;EZRlx`JA8b{_s<2fNUxtGn60=o}pefmu@I>&iLvrLn zfM+&jU%;{0m+;f_6t!a zY81|H-h*xlzAG>iW{#m^5c(5=*5Cu+elugQgN@zV?{bt#+%&hmU4^P}c>b{|)gGZg z-xmRn6+RjZE|cPW0-+PWR^XryN<_MMa<9biZgT4J2LiA+Ie(fT3sCaWj5dbqFFap+ zO)qz92umv|+(pqw+v(}hP0_&985+G3)G*gc=42^nZKT47glKHz;hQW;;>TF)Uc&FG zazPDfGIgdD&ac$sduUV9J%=Z<8}*~L=0m$+5ky9Q^UZpO-SzWg6^w_vx AIsgCw literal 0 HcmV?d00001