├── LICENSE.md ├── Plugin.php ├── README.md ├── assets ├── css │ └── notifications.css └── js │ └── notifications.js ├── behaviors └── UserPlusModel.php ├── classes └── ExtendUserPlugin.php ├── components ├── AddressBook.php ├── Notifications.php ├── addressbook │ └── form-select-address.htm └── notifications │ ├── default.htm │ └── notifications-list.htm ├── composer.json ├── config └── config.php ├── lang ├── ar.json ├── cs.json ├── de.json ├── en.json ├── es.json ├── fa.json ├── fr.json ├── hu.json ├── id.json ├── it.json ├── nl.json ├── pl.json ├── pt-br.json ├── ru.json ├── si.json ├── sk.json ├── sl.json ├── sv.json ├── tr.json ├── zh-cn.json └── zh-tw.json ├── models ├── UserAddress.php ├── UserNotification.php └── useraddress │ ├── HasModelAttributes.php │ ├── columns.yaml │ └── fields.yaml ├── partials ├── _user_address_book.php └── _user_profile.php └── updates ├── 000001_create_user_notifications.php ├── 000002_create_user_addresses.php ├── extend_users_table.php └── version.yaml /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | This plugin is an official extension of the October CMS platform and is free to use if you have a platform license. 4 | 5 | See End User License Agreement at https://octobercms.com/eula for more details. 6 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | "User Plus+", 26 | 'description' => "Adds profile fields to users.", 27 | 'author' => 'Alexey Bobkov, Samuel Georges', 28 | 'icon' => 'icon-user-plus', 29 | 'homepage' => 'https://github.com/rainlab/userplus-plugin' 30 | ]; 31 | } 32 | 33 | /** 34 | * boot 35 | */ 36 | public function boot() 37 | { 38 | Event::subscribe(\RainLab\UserPlus\Classes\ExtendUserPlugin::class); 39 | } 40 | 41 | /** 42 | * registerComponents 43 | */ 44 | public function registerComponents() 45 | { 46 | return [ 47 | \RainLab\UserPlus\Components\AddressBook::class => 'addressBook', 48 | \RainLab\UserPlus\Components\Notifications::class => 'notifications', 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # User Plus+ plugin 2 | 3 | This plugin extends the [User plugin](https://octobercms.com/plugin/rainlab-user) with extra profile fields and features. 4 | 5 | - Adds the following extra fields to a user: `company`, `phone`, `address_line1`, `address_line2`, `city`, `zip`, `state_id`, `country_id`. 6 | - Adds an Address Book to store multiple addresses for a user. 7 | - A user can belong to a Country and/or State, sourced from the [Location plugin](https://octobercms.com/plugin/rainlab-location). 8 | 9 | View this plugin on the October CMS marketplace: 10 | 11 | - https://octobercms.com/plugin/rainlab-userplus 12 | 13 | ### Address Book Component 14 | 15 | The `addressBook` component is used to manage multiple addresses for a user. Enable or disable the address book using the **System → User Settings → Profile** page. 16 | 17 | To see an example of the usage, we recommend installing this plugin with the `RainLab.Vanilla` theme. 18 | 19 | - https://github.com/rainlab/vanilla-theme 20 | 21 | ### Notifications Component 22 | 23 | The `notifications` component is used to display notifications assigned to the user, it allows them to mark notifications as read. It is best to add this component to the layout, the component will render the popover in a hidden state. 24 | 25 | ```twig 26 | {% component 'notifications' %} 27 | ``` 28 | 29 | The link to display notifications can be anywhere, and should have the `data-notifications-toggle` attribute to trigger the popover. 30 | 31 | ```html 32 | 38 | ``` 39 | 40 | You may also display a counter with the `notifications.hasUnread` check. 41 | 42 | ```twig 43 | {% if notifications.hasUnread %} 44 | {{ notifications.unreadCount }} 45 | {% endif %} 46 | ``` 47 | 48 | ### License 49 | 50 | This plugin is an official extension of the October CMS platform and is free to use if you have a platform license. See [EULA license](LICENSE.md) for more details. 51 | -------------------------------------------------------------------------------- /assets/css/notifications.css: -------------------------------------------------------------------------------- 1 | .control-user-notifications { 2 | position: relative; 3 | 4 | .notifications-popover { 5 | background: #fff; 6 | border: 1px solid #f0f0f0; 7 | position: absolute; 8 | width: 480px; 9 | z-index: 1050; 10 | right: 0; 11 | display: none; 12 | } 13 | 14 | &.active .notifications-popover { 15 | display: block; 16 | } 17 | 18 | .notifications-header { 19 | border-bottom: 1px solid #f0f0f0; 20 | padding: 15px; 21 | display: flex; 22 | align-items: center; 23 | 24 | h4 { 25 | padding: 0; 26 | margin: 0; 27 | flex-grow: 1; 28 | } 29 | 30 | .btn-close, .mark-all-read { 31 | margin: 0 5px; 32 | } 33 | } 34 | 35 | .notifications-content { 36 | .notifications-loading { 37 | padding: 20px; 38 | font-size: 24px; 39 | text-align: center; 40 | 41 | span:after { 42 | content: ''; 43 | display: inline-block; 44 | vertical-align: middle; 45 | margin-left: .4em; 46 | height: 1em; 47 | width: 1em; 48 | animation: spin 0.8s infinite linear; 49 | border: .2em solid currentColor; 50 | border-right-color: transparent; 51 | border-radius: 50%; 52 | opacity: .5; 53 | } 54 | } 55 | 56 | .no-notifications { 57 | text-align: center; 58 | padding: 25px; 59 | margin: 0; 60 | } 61 | 62 | > ul { 63 | padding: 0; 64 | margin: 0; 65 | list-style: none; 66 | overflow: auto; 67 | max-height: 400px; 68 | } 69 | 70 | > ul > li { 71 | position: relative; 72 | padding: 10px 25px 10px 60px; 73 | border-bottom: 1px solid #f9f9f9; 74 | 75 | > i { 76 | position: absolute; 77 | left: 25px; 78 | top: 12px; 79 | font-size: 18px; 80 | opacity: .5; 81 | } 82 | 83 | .parsed-body > *:last-child { 84 | margin-bottom: 0; 85 | } 86 | 87 | .date { 88 | white-space: nowrap; 89 | opacity: .75; 90 | } 91 | } 92 | } 93 | 94 | .notifications-footer { 95 | border-top: 1px solid #f0f0f0; 96 | padding: 15px; 97 | text-align: center; 98 | } 99 | } 100 | 101 | @keyframes spin { 102 | 0% { transform: rotate(0deg); } 103 | 100% { transform: rotate(359deg); } 104 | } 105 | -------------------------------------------------------------------------------- /assets/js/notifications.js: -------------------------------------------------------------------------------- 1 | oc.registerControl('user-notifications', class extends oc.ControlBase { 2 | init() { 3 | this.isActive = false; 4 | } 5 | 6 | connect() { 7 | this.$form = this.element.closest('form'); 8 | 9 | this.listen('click', '[data-notifications-load-more]', this.loadMoreNotifications); 10 | this.listen('click', '[data-notifications-mark-read-all]', this.markNotificationsAsRead); 11 | oc.Events.on(document, 'click', ':not([data-notifications-toggle])', this.proxy(this.clickOutsideNotificationsPopover)); 12 | oc.Events.on(document, 'click', '[data-notifications-toggle]', this.proxy(this.toggleNotificationsPopover)); 13 | } 14 | 15 | disconnect() { 16 | oc.Events.off(document, 'click', ':not([data-notifications-toggle])', this.proxy(this.clickOutsideNotificationsPopover)); 17 | oc.Events.off(document, 'click', '[data-notifications-toggle]', this.proxy(this.toggleNotificationsPopover)); 18 | } 19 | 20 | clickOutsideNotificationsPopover(ev) { 21 | if (!this.isActive) { 22 | return; 23 | } 24 | 25 | if (ev.target.closest('[data-control~="user-notifications"]')) { 26 | return; 27 | } 28 | 29 | this.toggleNotificationsPopover(); 30 | } 31 | 32 | toggleNotificationsPopover() { 33 | if (this.isActive) { 34 | this.isActive = false; 35 | this.$form.classList.remove('active'); 36 | return; 37 | } 38 | 39 | this.isActive = true; 40 | this.$form.classList.add('active'); 41 | oc.request(this.element, 'onLoadNotifications', { 42 | update: { '@notifications-list': '#notificationsContent' } 43 | }); 44 | } 45 | 46 | loadMoreNotifications() { 47 | var height = this.$form.querySelector('ul.notifications')?.scrollHeight; 48 | 49 | oc.request(this.element, 'onLoadMoreNotifications', { 50 | update: { '@notifications-list': '#notificationsContent' }, 51 | afterUpdate: () => { 52 | this.$form.querySelector('ul.notifications')?.scrollTo({ top: height }); 53 | } 54 | }); 55 | } 56 | 57 | markNotificationsAsRead() { 58 | oc.request(this.element, 'onMarkAllNotificationsAsRead', { 59 | update: { '@notifications-list': '#notificationsContent' } 60 | }); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /behaviors/UserPlusModel.php: -------------------------------------------------------------------------------- 1 | addFillable([ 23 | 'address_line1', 24 | 'address_line2', 25 | 'company', 26 | 'phone', 27 | 'city', 28 | 'zip', 29 | ]); 30 | 31 | $model->hasOne['primary_address'] = [ 32 | \RainLab\UserPlus\Models\UserAddress::class, 33 | 'conditions' => 'is_default = true', 34 | 'default' => ['is_default' => true] 35 | ]; 36 | 37 | $model->hasMany['addresses'] = [ 38 | \RainLab\UserPlus\Models\UserAddress::class, 39 | 'order' => 'is_default desc' 40 | ]; 41 | 42 | $model->hasMany['notifications'] = [ 43 | \RainLab\UserPlus\Models\UserNotification::class, 44 | 'order' => 'created_at desc' 45 | ]; 46 | } 47 | 48 | /** 49 | * getStreetAddressAttribute 50 | */ 51 | public function getStreetAddressAttribute() 52 | { 53 | return "{$this->model->address_line1}\n{$this->model->address_line2}"; 54 | } 55 | 56 | /** 57 | * setStreetAddressAttribute 58 | */ 59 | public function setStreetAddressAttribute($address) 60 | { 61 | $parts = explode("\n", $address, 2); 62 | $this->model->attributes['address_line1'] = $parts[0]; 63 | $this->model->attributes['address_line2'] = $parts[1] ?? ''; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /classes/ExtendUserPlugin.php: -------------------------------------------------------------------------------- 1 | extendUserModel(); 17 | 18 | // User 19 | 20 | $events->listen('rainlab.user.view.extendPreviewTabs', [static::class, 'extendPreviewTabs']); 21 | 22 | $events->listen('backend.form.extendFields', [static::class, 'extendUserFormFields']); 23 | 24 | $events->listen('backend.list.extendColumns', [static::class, 'extendUserListColumns']); 25 | 26 | $events->listen('backend.filter.extendScopes', [static::class, 'extendUserFilterScopes']); 27 | 28 | // Settings 29 | 30 | $this->extendSettingModel(); 31 | 32 | $events->listen('backend.form.extendFields', [static::class, 'extendSettingFormFields']); 33 | } 34 | 35 | /** 36 | * extendUserModel 37 | */ 38 | public function extendUserModel() 39 | { 40 | ExtensionContainer::extendClass(\RainLab\User\Models\User::class, static function($model) { 41 | $model->implementClassWith(\RainLab\Location\Behaviors\LocationModel::class); 42 | $model->implementClassWith(\RainLab\UserPlus\Behaviors\UserPlusModel::class); 43 | }); 44 | } 45 | 46 | /** 47 | * extendSettingModel 48 | */ 49 | public function extendSettingModel() 50 | { 51 | ExtensionContainer::extendClass(\RainLab\User\Models\Setting::class, static function($model) { 52 | $model->bindEvent('model.initSettingsData', function() use ($model) { 53 | $model->use_address_book = Config::get('rainlab.userplus::use_address_book', true); 54 | }); 55 | }); 56 | } 57 | 58 | /** 59 | * extendPreviewTabs 60 | */ 61 | public function extendPreviewTabs() 62 | { 63 | return [ 64 | "Profile" => $this->checkUseAddressBook() 65 | ? '$/rainlab/userplus/partials/_user_address_book.php' 66 | : '$/rainlab/userplus/partials/_user_profile.php' 67 | ]; 68 | } 69 | 70 | /** 71 | * extendSettingFormFields 72 | */ 73 | public function extendSettingFormFields(\Backend\Widgets\Form $widget) 74 | { 75 | if (!$this->checkControllerMatchesSetting($widget)) { 76 | return; 77 | } 78 | 79 | $widget->addTabField('use_address_book', 'Use Address Book')->displayAs('switch')->tab("Profile")->span('full') 80 | ->comment("Allow users to manage multiple addresses, otherwise users can provide only a single address."); 81 | } 82 | 83 | /** 84 | * extendUserFormFields 85 | */ 86 | public function extendUserFormFields(\Backend\Widgets\Form $widget) 87 | { 88 | if ($widget->isNested || !$this->checkControllerMatchesUser($widget)) { 89 | return; 90 | } 91 | 92 | if ($this->checkUseAddressBook()) { 93 | $addressBook = $widget->addTabField('addresses')->displayAs('relation')->tab("Address Book")->span('full') 94 | ->controller([ 95 | 'label' => 'Address', 96 | 'list' => '$/rainlab/userplus/models/useraddress/columns.yaml', 97 | 'form' => '$/rainlab/userplus/models/useraddress/fields.yaml', 98 | ]); 99 | 100 | if ($widget->getContext() !== 'preview') { 101 | $addressBook->label('Define addresses for this user, these can be chosen to prefill locations.'); 102 | } 103 | } 104 | else { 105 | $widget->addTabField('company', 'Company')->tab("Profile")->span('full'); 106 | $widget->addTabField('phone', 'Phone')->tab("Profile")->span('full'); 107 | $widget->addTabField('street_address', 'Street Address')->displayAs('textarea')->size('tiny')->tab("Profile")->span('full'); 108 | $widget->addTabField('city', 'City')->tab("Profile")->span('auto'); 109 | $widget->addTabField('zip', 'Zip')->tab("Profile")->span('auto'); 110 | $widget->addTabField('country', 'Country')->tab("Profile")->span('auto')->displayAs('dropdown')->placeholder("-- select state --"); 111 | $widget->addTabField('state', 'State')->tab("Profile")->span('auto')->displayAs('dropdown')->dependsOn('country')->placeholder("-- select state --"); 112 | } 113 | } 114 | 115 | /** 116 | * extendUserListColumns 117 | */ 118 | public function extendUserListColumns(\Backend\Widgets\Lists $widget) 119 | { 120 | if (!$this->checkControllerMatchesUser($widget)) { 121 | return; 122 | } 123 | 124 | $widget->defineColumn('company', "Company")->after('email')->searchable(); 125 | $widget->defineColumn('phone', "Phone")->after('email')->searchable(); 126 | $widget->defineColumn('city', "City")->after('email')->searchable()->invisible(); 127 | $widget->defineColumn('zip', "Zip")->after('email')->searchable()->invisible(); 128 | $widget->defineColumn('state', "State")->after('email')->invisible()->relation('state')->select('name'); 129 | $widget->defineColumn('country', "Country")->after('email')->invisible()->relation('country')->select('name'); 130 | } 131 | 132 | /** 133 | * extendUserFilterScopes 134 | */ 135 | public function extendUserFilterScopes(\Backend\Widgets\Filter $widget) 136 | { 137 | if (!$this->checkControllerMatchesUser($widget)) { 138 | return; 139 | } 140 | 141 | $widget->defineScope('country', "Country")->after('created_at')->displayAs('group')->emptyOption("Unspecified"); 142 | $widget->defineScope('state', "State")->after('created_at')->displayAs('group')->optionsMethod('getStateOptionsForFilter')->dependsOn('country')->emptyOption("Unspecified"); 143 | } 144 | 145 | /** 146 | * checkUseAddressBook 147 | */ 148 | protected function checkUseAddressBook(): bool 149 | { 150 | return \RainLab\User\Models\Setting::get('use_address_book', true); 151 | } 152 | 153 | /** 154 | * checkControllerMatchesUser 155 | */ 156 | protected function checkControllerMatchesUser($widget): bool 157 | { 158 | return $widget->getController() instanceof \RainLab\User\Controllers\Users && 159 | $widget->getModel() instanceof \RainLab\User\Models\User; 160 | } 161 | 162 | /** 163 | * checkControllerMatchesSetting 164 | */ 165 | protected function checkControllerMatchesSetting($widget): bool 166 | { 167 | return $widget->getController() instanceof \System\Controllers\Settings && 168 | $widget->getModel() instanceof \RainLab\User\Models\Setting; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /components/AddressBook.php: -------------------------------------------------------------------------------- 1 | "Address Book", 49 | 'description' => "Manages addresses for the user." 50 | ]; 51 | } 52 | 53 | /** 54 | * defineProperties 55 | */ 56 | public function defineProperties() 57 | { 58 | return []; 59 | } 60 | 61 | /** 62 | * onUpdateAddress 63 | */ 64 | public function onUpdateAddress() 65 | { 66 | $user = $this->user(); 67 | if (!$user) { 68 | throw new ForbiddenException; 69 | } 70 | 71 | // Create new address 72 | if (post('address_create')) { 73 | $address = $user->addresses()->make(); 74 | } 75 | // Lookup existing address 76 | else { 77 | $addressId = post('address_id'); 78 | if (!$addressId || !$user->addresses) { 79 | throw new ApplicationException(__("Address not found.")); 80 | } 81 | 82 | $address = $user->addresses->find(post('address_id')); 83 | if (!$address) { 84 | throw new ApplicationException(__("Address not found.")); 85 | } 86 | } 87 | 88 | // Update or delete address 89 | if (post('address_delete') && $address->exists) { 90 | $address->delete(); 91 | } 92 | else { 93 | $address->fill(array_except(post(), ['address_id', 'address_create'])); 94 | $address->save(); 95 | } 96 | 97 | // Refresh addresses stored in memory 98 | $user->unsetRelations(); 99 | 100 | if ($flash = Cms::flashFromPost(__("Your address book has been updated."))) { 101 | Flash::success($flash); 102 | } 103 | 104 | if ($redirect = Cms::redirectFromPost()) { 105 | return $redirect; 106 | } 107 | } 108 | 109 | /** 110 | * user returns the logged in user 111 | */ 112 | public function user(): ?User 113 | { 114 | return Auth::user(); 115 | } 116 | 117 | /** 118 | * addresses returns addresses owned by the user 119 | */ 120 | public function addresses() 121 | { 122 | return $this->addresses ??= Auth::user()?->addresses; 123 | } 124 | 125 | /** 126 | * hasAddresses 127 | */ 128 | public function hasAddresses() 129 | { 130 | return count($this->addresses() ?: []) > 0; 131 | } 132 | 133 | /** 134 | * useAddressBook 135 | */ 136 | public function useAddressBook(): bool 137 | { 138 | return \RainLab\User\Models\Setting::get('use_address_book', true); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /components/Notifications.php: -------------------------------------------------------------------------------- 1 | "Notifications Component", 26 | 'description' => "Display user and site-wide notifications" 27 | ]; 28 | } 29 | 30 | /** 31 | * defineProperties 32 | */ 33 | public function defineProperties() 34 | { 35 | return [ 36 | 'recordsPerPage' => [ 37 | 'title' => 'Records Per Page', 38 | 'comment' => 'Number of notifications to display per page', 39 | 'default' => 7 40 | ], 41 | 'includeAssets' => [ 42 | 'title' => 'Include Assets', 43 | 'comment' => 'Inject the JavaScript and Stylesheet used by the default component markup', 44 | 'type' => 'checkbox', 45 | 'default' => true 46 | ] 47 | ]; 48 | } 49 | 50 | /** 51 | * onRun 52 | */ 53 | public function onRun() 54 | { 55 | if (!$this->user()) { 56 | return; 57 | } 58 | 59 | if ($this->property('includeAssets')) { 60 | $this->addCss('assets/css/notifications.css'); 61 | $this->addJs('assets/js/notifications.js'); 62 | } 63 | 64 | $this->prepareVars(); 65 | } 66 | 67 | /** 68 | * prepareVars 69 | */ 70 | protected function prepareVars() 71 | { 72 | $this->page['notificationToDisplay'] = $this->getRecordCountToDisplay(); 73 | } 74 | 75 | /** 76 | * user returns the logged in user 77 | */ 78 | public function user(): ?User 79 | { 80 | return Auth::user(); 81 | } 82 | 83 | /** 84 | * hasUnread 85 | */ 86 | public function hasUnread() 87 | { 88 | return $this->unreadCount() > 0; 89 | } 90 | 91 | /** 92 | * unreadCount 93 | */ 94 | public function unreadCount() 95 | { 96 | return $this->countCache ??= $this->getUnreadQuery()->count(); 97 | } 98 | 99 | /** 100 | * unreadNotifications 101 | */ 102 | public function unreadNotifications($notificationToDisplay = null) 103 | { 104 | if (!$notificationToDisplay) { 105 | $notificationToDisplay = $this->getRecordCountToDisplay(); 106 | } 107 | 108 | return $this->getUnreadQuery()->paginate($notificationToDisplay); 109 | } 110 | 111 | /** 112 | * onLoadNotifications handler 113 | */ 114 | public function onLoadNotifications() 115 | { 116 | $this->prepareVars(); 117 | $this->page['notifications'] = $this->unreadNotifications(); 118 | } 119 | 120 | /** 121 | * onLoadMoreNotifications handler 122 | */ 123 | public function onLoadMoreNotifications() 124 | { 125 | $notificationToDisplay = $this->getRecordCountToDisplay() + $this->property('recordsPerPage'); 126 | 127 | $this->page['notificationToDisplay'] = $notificationToDisplay; 128 | $this->page['notifications'] = $this->unreadNotifications($notificationToDisplay); 129 | } 130 | 131 | /** 132 | * onMarkAllNotificationsAsRead handler 133 | */ 134 | public function onMarkAllNotificationsAsRead() 135 | { 136 | $this->getUnreadQuery()->update(['read_at' => Carbon::now()]); 137 | 138 | $this->prepareVars(); 139 | 140 | $this->page['notifications'] = $this->unreadNotifications(); 141 | 142 | $this->dispatchBrowserEvent('user:notification-count', ['unreadCount' => 0]); 143 | } 144 | 145 | /** 146 | * getRecordCountToDisplay 147 | */ 148 | protected function getRecordCountToDisplay() 149 | { 150 | return ((int) post('records_per_page')) ?: $this->property('recordsPerPage'); 151 | } 152 | 153 | /** 154 | * getUnreadQuery 155 | */ 156 | protected function getUnreadQuery() 157 | { 158 | if (!$user = Auth::getUser()) { 159 | throw new ApplicationException('You must be logged in'); 160 | } 161 | 162 | return $user->notifications()->applyUnread(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /components/addressbook/form-select-address.htm: -------------------------------------------------------------------------------- 1 | {% set addressBookId = addressBookId|default(post('address_book_id')) %} 2 | {% set addresses = user.addresses|default([]) %} 3 | 10 | -------------------------------------------------------------------------------- /components/notifications/default.htm: -------------------------------------------------------------------------------- 1 | {% if __SELF__.user %} 2 |
3 |
4 |
5 |

Notifications

6 | 7 | 8 | Mark all as read 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | {% endif %} 22 | -------------------------------------------------------------------------------- /components/notifications/notifications-list.htm: -------------------------------------------------------------------------------- 1 | {% if notifications.total %} 2 | 11 | 12 | {% if notifications.lastItem() < notifications.total %} 13 | 18 | {% endif %} 19 | 20 | 21 | {% else %} 22 |

There are no notifications for you

23 | {% endif %} 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rainlab/userplus-plugin", 3 | "type": "october-plugin", 4 | "description": "User plus plugin for October CMS", 5 | "homepage": "https://octobercms.com/plugin/rainlab-userplus", 6 | "keywords": ["october", "octobercms", "blog", "userplus", "user"], 7 | "license": "proprietary", 8 | "authors": [ 9 | { 10 | "name": "Alexey Bobkov", 11 | "email": "aleksey.bobkov@gmail.com", 12 | "role": "Co-founder" 13 | }, 14 | { 15 | "name": "Samuel Georges", 16 | "email": "daftspunky@gmail.com", 17 | "role": "Co-founder" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.0.2", 22 | "composer/installers": "~1.0" 23 | }, 24 | "minimum-stability": "dev" 25 | } 26 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | true, 15 | ]; 16 | -------------------------------------------------------------------------------- /lang/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "المستخدم بلس+", 3 | "Adds profile fields to users.": "إضافة حقول الملف الشخصي للمستخدمين.", 4 | "Phone": "هاتف", 5 | "Profile": "حساب تعريفي", 6 | "Mobile": "متحرك", 7 | "Company": "شركة", 8 | "Street Address": "عنوان الشارع", 9 | "City": "مدينة", 10 | "Zip": "أَزِيز", 11 | "To First Name": "إلى الاسم الأول", 12 | "To Last Name": "إلى الاسم الأخير", 13 | "Leave this field blank if it the same as the current the user.": "اترك هذا الحقل فارغًا إذا كان هو نفس المستخدم الحالي.", 14 | "Address": "عنوان", 15 | "Address Book": "دفتر العناوين" 16 | } -------------------------------------------------------------------------------- /lang/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "User Plus+", 3 | "Adds profile fields to users.": "Přidává uživatelům profilová políčka.", 4 | "Phone": "Telefon", 5 | "Profile": "Profil", 6 | "Mobile": "Mobil", 7 | "Company": "Firma", 8 | "Street Address": "Ulice", 9 | "City": "Město", 10 | "Zip": "PSČ", 11 | "To First Name": "K křestnímu jménu", 12 | "To Last Name": "K příjmení", 13 | "Leave this field blank if it the same as the current the user.": "Nechte toto pole prázdné, pokud je stejné jako aktuální uživatel.", 14 | "Address": "Adresa", 15 | "Address Book": "Adresář" 16 | } -------------------------------------------------------------------------------- /lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Benutzer Plus+", 3 | "Adds profile fields to users.": "Fügt Benutzern Profilfelder hinzu.", 4 | "Phone": "Telefon", 5 | "Profile": "Profil", 6 | "Mobile": "Mobile", 7 | "Company": "Unternehmen", 8 | "Street Address": "Straßenadresse", 9 | "City": "Stadt", 10 | "Zip": "Reißverschluss", 11 | "To First Name": "An Vorname", 12 | "To Last Name": "An Nachname", 13 | "Leave this field blank if it the same as the current the user.": "Lassen Sie dieses Feld leer, wenn es mit dem aktuellen Benutzer identisch ist.", 14 | "Address": "Adresse", 15 | "Address Book": "Adressbuch" 16 | } -------------------------------------------------------------------------------- /lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "User Plus+", 3 | "Adds profile fields to users.": "Adds profile fields to users.", 4 | "Phone": "Phone", 5 | "Profile": "Profile", 6 | "Mobile": "Mobile", 7 | "Company": "Company", 8 | "Street Address": "Street Address", 9 | "City": "City", 10 | "Zip": "Zip", 11 | "To First Name": "To First Name", 12 | "To Last Name": "To Last Name", 13 | "Leave this field blank if it the same as the current the user.": "Leave this field blank if it the same as the current the user.", 14 | "Address": "Address", 15 | "Address Book": "Address Book" 16 | } -------------------------------------------------------------------------------- /lang/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Usuario Plus+", 3 | "Adds profile fields to users.": "Agrega campos de perfil a los usuarios.", 4 | "Phone": "Teléfono", 5 | "Profile": "Perfil", 6 | "Mobile": "Móvil", 7 | "Company": "Compañía", 8 | "Street Address": "Dirección de la calle", 9 | "City": "Ciudad", 10 | "Zip": "Cremallera", 11 | "To First Name": "Al nombre de pila", 12 | "To Last Name": "Al apellido", 13 | "Leave this field blank if it the same as the current the user.": "Deje este campo en blanco si es el mismo que el del usuario actual.", 14 | "Address": "DIRECCIÓN", 15 | "Address Book": "Libreta de direcciones" 16 | } -------------------------------------------------------------------------------- /lang/fa.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "گسترش کاربر", 3 | "Adds profile fields to users.": "افزودن فیلد به پروفایل کاربران", 4 | "Phone": "تلفن", 5 | "Profile": "نمایه", 6 | "Mobile": "تلفن همراه", 7 | "Company": "شرکت", 8 | "Street Address": "آدرس", 9 | "City": "شهر", 10 | "Zip": "کد پستی", 11 | "To First Name": "به نام", 12 | "To Last Name": "به نام خانوادگی", 13 | "Leave this field blank if it the same as the current the user.": "اگر با فیلد فعلی کاربر یکی است، این قسمت را خالی بگذارید.", 14 | "Address": "آدرس", 15 | "Address Book": "دفترچه آدرس" 16 | } -------------------------------------------------------------------------------- /lang/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "User Plus+", 3 | "Adds profile fields to users.": "Ajoute des champs de profil aux utilisateurs.", 4 | "Phone": "Téléphone", 5 | "Profile": "Profil", 6 | "Mobile": "Mobile", 7 | "Company": "Société", 8 | "Street Address": "Adresse", 9 | "City": "Ville", 10 | "Zip": "Code postal", 11 | "To First Name": "Au prénom", 12 | "To Last Name": "Au nom de famille", 13 | "Leave this field blank if it the same as the current the user.": "Laissez ce champ vide s'il est identique à celui de l'utilisateur actuel.", 14 | "Address": "Adresse", 15 | "Address Book": "Carnet d'adresses" 16 | } -------------------------------------------------------------------------------- /lang/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Felhasználók Plusz+", 3 | "Adds profile fields to users.": "Új személyes mezők a felhasználók adatlapjához.", 4 | "Phone": "Telefon", 5 | "Profile": "Személyes", 6 | "Mobile": "Mobil", 7 | "Company": "Cég", 8 | "Street Address": "Postacím", 9 | "City": "Város", 10 | "Zip": "Irszám.", 11 | "To First Name": "Keresztnévre", 12 | "To Last Name": "Vezetéknévre", 13 | "Leave this field blank if it the same as the current the user.": "Hagyja üresen ezt a mezőt, ha megegyezik a jelenlegi felhasználóval.", 14 | "Address": "Cím", 15 | "Address Book": "Címjegyzék" 16 | } -------------------------------------------------------------------------------- /lang/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Pengguna Plus+", 3 | "Adds profile fields to users.": "Menambahkan bidang profil ke pengguna.", 4 | "Phone": "Telepon", 5 | "Profile": "Profil", 6 | "Mobile": "Seluler", 7 | "Company": "Perusahaan", 8 | "Street Address": "Alamat Jalan", 9 | "City": "Kota", 10 | "Zip": "Ritsleting", 11 | "To First Name": "Untuk Nama Depan", 12 | "To Last Name": "Untuk Nama Belakang", 13 | "Leave this field blank if it the same as the current the user.": "Biarkan kolom ini kosong jika sama dengan pengguna saat ini.", 14 | "Address": "Alamat", 15 | "Address Book": "Buku Alamat" 16 | } -------------------------------------------------------------------------------- /lang/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Utente Plus+", 3 | "Adds profile fields to users.": "Aggiunge campi del profilo agli utenti.", 4 | "Phone": "Telefono", 5 | "Profile": "Profilo", 6 | "Mobile": "Mobile", 7 | "Company": "Azienda", 8 | "Street Address": "Indirizzo stradale", 9 | "City": "Città", 10 | "Zip": "Cerniera lampo", 11 | "To First Name": "Al Nome di battesimo", 12 | "To Last Name": "Al cognome", 13 | "Leave this field blank if it the same as the current the user.": "Lasciare vuoto questo campo se è uguale all'utente attuale.", 14 | "Address": "Indirizzo", 15 | "Address Book": "Rubrica" 16 | } -------------------------------------------------------------------------------- /lang/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Gebruiker Plus+", 3 | "Adds profile fields to users.": "Voegt profielvelden toe aan gebruikers.", 4 | "Phone": "Telefoon", 5 | "Profile": "Profiel", 6 | "Mobile": "Mobiel", 7 | "Company": "Bedrijf", 8 | "Street Address": "Adres", 9 | "City": "Stad", 10 | "Zip": "Postcode", 11 | "To First Name": "Naar Voornaam", 12 | "To Last Name": "Naar Achternaam", 13 | "Leave this field blank if it the same as the current the user.": "Laat dit veld leeg als het hetzelfde is als de huidige gebruiker.", 14 | "Address": "Adres", 15 | "Address Book": "Adresboek" 16 | } -------------------------------------------------------------------------------- /lang/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Użytkownik Plus+", 3 | "Adds profile fields to users.": "Dodaje pola profilu dla użytkowników.", 4 | "Phone": "Telefon", 5 | "Profile": "Profil", 6 | "Mobile": "Przenośny", 7 | "Company": "Firma", 8 | "Street Address": "Adres uliczny", 9 | "City": "Miasto", 10 | "Zip": "Zamek błyskawiczny", 11 | "To First Name": "Do Imienia", 12 | "To Last Name": "Do nazwiska", 13 | "Leave this field blank if it the same as the current the user.": "Pozostaw to pole puste, jeśli jest to ten sam użytkownik, co obecny.", 14 | "Address": "Adres", 15 | "Address Book": "Książka adresowa" 16 | } -------------------------------------------------------------------------------- /lang/pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Usuário Plus+", 3 | "Adds profile fields to users.": "Adiciona campos de perfil aos usuários.", 4 | "Phone": "Telefone", 5 | "Profile": "Perfil", 6 | "Mobile": "Celular", 7 | "Company": "Empresa", 8 | "Street Address": "Endereço", 9 | "City": "Cidade", 10 | "Zip": "Cep" 11 | } -------------------------------------------------------------------------------- /lang/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Пользователь Плюс+", 3 | "Adds profile fields to users.": "Добавляет поля профиля пользователям.", 4 | "Phone": "Телефон", 5 | "Profile": "Профиль", 6 | "Mobile": "Мобильный", 7 | "Company": "Компания", 8 | "Street Address": "Адрес улицы", 9 | "City": "Город", 10 | "Zip": "Почтовый индекс", 11 | "To First Name": "Имя", 12 | "To Last Name": "По Фамилии", 13 | "Leave this field blank if it the same as the current the user.": "Оставьте это поле пустым, если оно совпадает с текущим пользователем.", 14 | "Address": "Адрес", 15 | "Address Book": "Адресная книга" 16 | } -------------------------------------------------------------------------------- /lang/si.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "පරිශීලක ප්ලස් +", 3 | "Adds profile fields to users.": "පරිශීලකයින්ට පැතිකඩ ක්ෂේත්ර එක් කරයි.", 4 | "Phone": "දුරකථනය", 5 | "Profile": "පැතිකඩ", 6 | "Mobile": "ජංගම", 7 | "Company": "සමාගම", 8 | "Street Address": "වීථි ලිපිනය", 9 | "City": "නගරය", 10 | "Zip": "Zip", 11 | "To First Name": "මුල් නමට", 12 | "To Last Name": "අවසාන නමට", 13 | "Leave this field blank if it the same as the current the user.": "එය වත්මන් පරිශීලකයාට සමාන නම් මෙම ක්ෂේත්‍රය හිස්ව තබන්න.", 14 | "Address": "ලිපිනය", 15 | "Address Book": "ලිපින පොත" 16 | } -------------------------------------------------------------------------------- /lang/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Používateľ Plus+", 3 | "Adds profile fields to users.": "Pridá používateľom polia profilu.", 4 | "Phone": "Telefón", 5 | "Profile": "Profil", 6 | "Mobile": "Mobil", 7 | "Company": "Spoločnosť", 8 | "Street Address": "Ulica", 9 | "City": "Mesto", 10 | "Zip": "zips", 11 | "To First Name": "Krstnému menu", 12 | "To Last Name": "K priezvisku", 13 | "Leave this field blank if it the same as the current the user.": "Nechajte toto pole prázdne, ak je rovnaké ako aktuálny používateľ.", 14 | "Address": "Adresa", 15 | "Address Book": "Adresár" 16 | } -------------------------------------------------------------------------------- /lang/sl.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Uporabniki Plus+", 3 | "Adds profile fields to users.": "Razširitev vtičnika Uporabniki z dodatnimi polji za profil.", 4 | "Phone": "Telefon", 5 | "Profile": "Profil", 6 | "Mobile": "Mobilni", 7 | "Company": "Podjetje", 8 | "Street Address": "Naslov", 9 | "City": "Mesto", 10 | "Zip": "Pošta", 11 | "To First Name": "Na ime", 12 | "To Last Name": "Na priimek", 13 | "Leave this field blank if it the same as the current the user.": "Pustite to polje prazno, če je isto kot trenutni uporabnik.", 14 | "Address": "Naslov", 15 | "Address Book": "Imenik" 16 | } -------------------------------------------------------------------------------- /lang/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Användare Plus+", 3 | "Adds profile fields to users.": "Lägger till profilfält för användare.", 4 | "Phone": "Telefon", 5 | "Profile": "Profil", 6 | "Mobile": "Mobil", 7 | "Company": "Företag", 8 | "Street Address": "Gatuadress", 9 | "City": "Stad", 10 | "Zip": "Blixtlås", 11 | "To First Name": "Till Förnamn", 12 | "To Last Name": "Till efternamn", 13 | "Leave this field blank if it the same as the current the user.": "Lämna detta fält tomt om det är samma som den nuvarande användaren.", 14 | "Address": "Adress", 15 | "Address Book": "Adressbok" 16 | } -------------------------------------------------------------------------------- /lang/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "Üye Plus+", 3 | "Adds profile fields to users.": "Üyeler için ek profil bilgileri sağlar.", 4 | "Phone": "Telefon", 5 | "Profile": "Profil", 6 | "Mobile": "Cep telefon", 7 | "Company": "Firma", 8 | "Street Address": "Adresi", 9 | "City": "Şehir", 10 | "Zip": "Posta kodu", 11 | "To First Name": "Adına", 12 | "To Last Name": "Soyadına", 13 | "Leave this field blank if it the same as the current the user.": "Mevcut kullanıcıyla aynıysa bu alanı boş bırakın.", 14 | "Address": "Adres", 15 | "Address Book": "Adres Defteri" 16 | } -------------------------------------------------------------------------------- /lang/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "User Plus+", 3 | "Adds profile fields to users.": "为用户插件增加用户信息字段", 4 | "Phone": "手机", 5 | "Profile": "信息", 6 | "Mobile": "电话", 7 | "Company": "公司", 8 | "Street Address": "地址", 9 | "City": "城市", 10 | "Zip": "邮政编码" 11 | } -------------------------------------------------------------------------------- /lang/zh-tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "User Plus+": "會員+", 3 | "Adds profile fields to users.": "增加會員資料欄位。", 4 | "Phone": "電話", 5 | "Profile": "會員資料", 6 | "Mobile": "手機", 7 | "Company": "公司名稱", 8 | "Street Address": "地址", 9 | "City": "城市", 10 | "Zip": "郵遞區號" 11 | } -------------------------------------------------------------------------------- /models/UserAddress.php: -------------------------------------------------------------------------------- 1 | 'required', 48 | 'city' => 'required', 49 | 'zip' => 'required', 50 | 'country' => 'required', 51 | ]; 52 | 53 | /** 54 | * @var array fillable fields 55 | */ 56 | protected $fillable = [ 57 | 'first_name', 58 | 'last_name', 59 | 'company', 60 | 'phone', 61 | 'address_line1', 62 | 'address_line2', 63 | 'city', 64 | 'zip', 65 | 'state_id', 66 | 'country_id', 67 | 'is_business', 68 | 'is_default', 69 | ]; 70 | 71 | /** 72 | * @var array nullable attribute names which should be set to null when empty. 73 | */ 74 | protected $nullable = [ 75 | 'state_id', 76 | 'is_business', 77 | 'is_default' 78 | ]; 79 | 80 | /** 81 | * @var array belongsTo relations 82 | */ 83 | public $belongsTo = [ 84 | 'user' => User::class 85 | ]; 86 | 87 | /** 88 | * afterSave 89 | */ 90 | public function afterSave() 91 | { 92 | if ($this->is_default && $this->user_id) { 93 | $this->user()->update([ 94 | 'company' => $this->company, 95 | 'address_line1' => $this->address_line1, 96 | 'address_line2' => $this->address_line2, 97 | 'city' => $this->city, 98 | 'zip' => $this->zip, 99 | 'state_id' => $this->state_id, 100 | 'country_id' => $this->country_id, 101 | 'phone' => $this->phone, 102 | ]); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /models/UserNotification.php: -------------------------------------------------------------------------------- 1 | User::class, 52 | ]; 53 | 54 | /** 55 | * createRecord adds a notification for a user 56 | */ 57 | public static function createRecord($userId, $type, $body, $data = null) 58 | { 59 | $obj = new static; 60 | $obj->user_id = $userId; 61 | $obj->type = $type; 62 | $obj->body = $body; 63 | if (is_array($data)) { 64 | $obj->data = $data; 65 | } 66 | $obj->save(); 67 | 68 | return $obj; 69 | } 70 | 71 | /** 72 | * markAsRead marks the notification as read. 73 | */ 74 | public function markAsRead() 75 | { 76 | if (is_null($this->read_at)) { 77 | $this->forceFill(['read_at' => $this->freshTimestamp()])->save(); 78 | } 79 | } 80 | 81 | /** 82 | * getIsReadAttribute determines if a notification has been read. 83 | */ 84 | public function getIsReadAttribute(): bool 85 | { 86 | return $this->read_at !== null; 87 | } 88 | 89 | /** 90 | * getIsUnreadAttribute determines if a notification has not been read. 91 | */ 92 | public function getIsUnreadAttribute(): bool 93 | { 94 | return $this->read_at === null; 95 | } 96 | 97 | /** 98 | * scopeApplyUnread get the user unread notifications. 99 | */ 100 | public function scopeApplyUnread($query) 101 | { 102 | return $query->whereNull('read_at'); 103 | } 104 | 105 | /** 106 | * scopeApplyRead gets the user read notifications. 107 | */ 108 | public function scopeApplyRead($query) 109 | { 110 | return $query->whereNotNull('read_at'); 111 | } 112 | 113 | /** 114 | * getParsedBodyAttribute get the parsed body of the announcement. 115 | */ 116 | public function getParsedBodyAttribute(): string 117 | { 118 | return Markdown::parse($this->body); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /models/useraddress/HasModelAttributes.php: -------------------------------------------------------------------------------- 1 | first_name} {$this->last_name}"; 21 | } 22 | 23 | /** 24 | * getStreetAddressAttribute 25 | */ 26 | public function getStreetAddressAttribute() 27 | { 28 | return "{$this->address_line1}\n{$this->address_line2}"; 29 | } 30 | 31 | /** 32 | * setStreetAddressAttribute 33 | */ 34 | public function setStreetAddressAttribute($address) 35 | { 36 | $parts = explode("\n", $address, 2); 37 | $this->attributes['address_line1'] = $parts[0]; 38 | $this->attributes['address_line2'] = $parts[1] ?? ''; 39 | } 40 | 41 | /** 42 | * getAddressFormattedAttribute 43 | */ 44 | public function getAddressFormattedAttribute() 45 | { 46 | if ($this->first_name || $this->last_name) { 47 | $addressFormat = ':address, :city :state_name :zip, :country_name (:first_name :last_name)'; 48 | } 49 | else { 50 | $addressFormat = ':address, :city :state_name :zip, :country_name'; 51 | } 52 | 53 | $address = $this->address_line1; 54 | if ($this->address_line2) { 55 | $address .= ', ' . $this->address_line2; 56 | } 57 | 58 | $string = strtr($addressFormat, [ 59 | ':first_name' => $this->first_name, 60 | ':last_name' => $this->last_name, 61 | ':address' => $address, 62 | ':city' => $this->city, 63 | ':zip' => $this->zip, 64 | ':state_name' => $this->state?->name, 65 | ':state_code' => $this->state?->code, 66 | ':country_name' => $this->country?->name, 67 | ':country_code' => $this->country?->code, 68 | ]); 69 | 70 | $string = preg_replace('/\s+/', ' ', $string); 71 | 72 | return $string; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /models/useraddress/columns.yaml: -------------------------------------------------------------------------------- 1 | # =================================== 2 | # Column Definitions 3 | # =================================== 4 | 5 | columns: 6 | address_formatted: 7 | label: Address 8 | sortable: false 9 | 10 | first_name: 11 | label: First Name 12 | invisible: true 13 | 14 | last_name: 15 | label: Last Name 16 | invisible: true 17 | 18 | company: 19 | label: Company 20 | invisible: true 21 | 22 | phone: 23 | label: Phone 24 | invisible: true 25 | 26 | address_line1: 27 | label: Address 28 | invisible: true 29 | 30 | city: 31 | label: City 32 | invisible: true 33 | 34 | zip: 35 | label: Zip 36 | invisible: true 37 | 38 | state: 39 | label: State 40 | relation: state 41 | select: name 42 | invisible: true 43 | 44 | country: 45 | label: Country 46 | relation: country 47 | select: name 48 | invisible: true 49 | 50 | is_default: 51 | label: Default 52 | type: switch 53 | -------------------------------------------------------------------------------- /models/useraddress/fields.yaml: -------------------------------------------------------------------------------- 1 | # =================================== 2 | # Field Definitions 3 | # =================================== 4 | 5 | fields: 6 | street_address: 7 | label: Address 8 | span: full 9 | type: textarea 10 | size: tiny 11 | cssClass: pb-2 12 | required: true 13 | autoFocus: true 14 | 15 | is_business: 16 | label: Location is a business address 17 | type: checkbox 18 | cssClass: pb-2 19 | 20 | city: 21 | label: City 22 | span: auto 23 | 24 | zip: 25 | label: Zip / Postal Code 26 | span: auto 27 | 28 | country: 29 | label: Country 30 | type: dropdown 31 | span: auto 32 | placeholder: -- select -- 33 | 34 | state: 35 | label: State 36 | type: dropdown 37 | span: auto 38 | dependsOn: country 39 | placeholder: -- select -- 40 | 41 | company: 42 | label: Company Name 43 | span: auto 44 | 45 | phone: 46 | label: Phone 47 | span: auto 48 | 49 | first_name: 50 | label: To First Name 51 | span: auto 52 | commentAbove: Leave this field blank if it the same as the current the user. 53 | 54 | last_name: 55 | label: To Last Name 56 | span: auto 57 | commentAbove: Leave this field blank if it the same as the current the user. 58 | 59 | is_default: 60 | label: Default 61 | comment: Use this address by default. 62 | type: checkbox 63 | -------------------------------------------------------------------------------- /partials/_user_address_book.php: -------------------------------------------------------------------------------- 1 |

2 | formRenderPrimaryTab('Address Book') ?> 3 | -------------------------------------------------------------------------------- /partials/_user_profile.php: -------------------------------------------------------------------------------- 1 |

2 | formRenderPrimaryTab('Profile') ?> 3 | -------------------------------------------------------------------------------- /updates/000001_create_user_notifications.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 12 | $table->bigInteger('user_id')->unsigned()->nullable()->index(); 13 | $table->string('baseid')->nullable()->index(); 14 | $table->string('type')->nullable(); 15 | $table->mediumText('body')->nullable(); 16 | $table->mediumText('data')->nullable(); 17 | $table->timestamp('read_at')->nullable(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::dropIfExists('rainlab_user_notifications'); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /updates/000002_create_user_addresses.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 12 | $table->string('first_name')->nullable(); 13 | $table->string('last_name')->nullable(); 14 | $table->string('company')->nullable(); 15 | $table->string('phone')->nullable(); 16 | $table->string('address_line1')->nullable(); 17 | $table->string('address_line2')->nullable(); 18 | $table->string('city')->nullable(); 19 | $table->string('zip')->nullable(); 20 | $table->bigInteger('state_id')->unsigned()->nullable(); 21 | $table->bigInteger('country_id')->unsigned()->nullable()->index(); 22 | $table->bigInteger('user_id')->unsigned()->nullable()->index(); 23 | $table->boolean('is_business')->default(false); 24 | $table->boolean('is_default')->default(false)->index(); 25 | $table->timestamp('deleted_at')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | public function down() 31 | { 32 | Schema::dropIfExists('user_addresses'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /updates/extend_users_table.php: -------------------------------------------------------------------------------- 1 | string('address_line1')->nullable(); 13 | $table->string('address_line2')->nullable(); 14 | $table->string('company')->nullable(); 15 | $table->string('phone')->nullable(); 16 | $table->string('city')->nullable(); 17 | $table->string('zip')->nullable(); 18 | }); 19 | } 20 | 21 | if (!Schema::hasColumn('users', 'country_id')) { 22 | Schema::table('users', function($table) { 23 | $table->bigInteger('state_id')->unsigned()->nullable()->index(); 24 | $table->bigInteger('country_id')->unsigned()->nullable()->index(); 25 | }); 26 | } 27 | } 28 | 29 | public function down() 30 | { 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /updates/version.yaml: -------------------------------------------------------------------------------- 1 | v1.0.1: First version of User Profile 2 | v1.0.2: Reintroduce profile fields that were removed from User plugin 3 | v1.0.3: Add mobile phone user profile field. 4 | v1.0.4: Add various languages and minor bug fixes. 5 | v1.1.0: Compatibility with Notify plugin. 6 | v1.2.0: Fixes support for October CMS 2.0 7 | v2.0.0: 8 | - Major Upgrade to User+ Plugin 9 | - 000001_create_user_notifications.php 10 | - 000002_create_user_addresses.php 11 | - extend_users_table.php 12 | v2.0.1: Fixes profile tab extension in user controller. 13 | v2.0.2: Improve address book user experience. 14 | --------------------------------------------------------------------------------