=FavouritesManager.php and FavouritesRoutes.php fixes. Moving all logic to FavouritesManager.php. Still some left to do
| | |
| | | return array_merge(array_keys($this->content), array_keys($this->taxonomies)); |
| | | } |
| | | |
| | | public function favourites(): FavouritesManager|false |
| | | { |
| | | return $this->managers['favourites'] ?? false; |
| | | } |
| | | |
| | | public function dashboard(): DashboardManager|false |
| | | { |
| | | return $this->managers['dash'] ?? false; |
| | |
| | | .replace{margin-left:var(--btn_)!important}.dashboard aside.main.left{bottom:0}.dashboard .qtoggle{left:0;bottom:0;margin:0!important}nav.sidebar{--wrap:nowrap;--align:flex-start;position:fixed;bottom:0;top:var(--btn);z-index:var(--z-4);height:calc(100% - var(--btn));background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);width:var(--btn);transition:var(--trans-size);overflow:hidden auto;margin-left:0!important;padding-bottom:var(--btn)}nav.sidebar.left{left:0}nav.sidebar.right{right:0}nav.sidebar .icon{--w:var(--chip_);width:var(--w);transition:var(--trans-size)}nav.sidebar.open{width:fit-content;max-width:85vw}nav.sidebar.open .icon{--w:var(--chip)}nav.sidebar ul{height:max-content;width:100%;--gap:0;--dir:column}nav.sidebar li{--justify:center;--wrap:nowrap;--align:flex-start;overflow:hidden;height:max-content}nav.sidebar ul ul{max-height:0;overflow:hidden;transform:scaleY(0);transform-origin:top;transition:var(--trans-base)}nav.sidebar li.open>ul{max-height:max-content;transform:scaleY(1)}nav.sidebar.open li>div{width:100%;padding-right:var(--btn)}nav.sidebar.open li.has-submenu>div{padding-right:0}nav.sidebar.open li.has-submenu>ul{padding-left:var(--chip)}nav.sidebar .title{display:none}nav.sidebar.open .title{display:block;white-space:nowrap}nav.sidebar a{--justify:flex-start}nav.sidebar .a{gap:.5rem;display:flex;width:100%;justify-content:flex-start;align-items:center;min-height:var(--btn);padding:var(--padding)}nav.sidebar .toggle:not(.main){display:none;width:var(--btn);height:var(--chipchip)}nav.sidebar.open .toggle{display:flex}nav.sidebar .toggle.main{position:fixed;left:unset;bottom:0;right:0;width:var(--btn);height:var(--btn);z-index:var(--z-8);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.all-filters{font-size:var(--txt-x-small)}.all-filters[open]{border:2px solid rgb(var(--action-0));padding:0;border-radius:0 0 var(--radius-outer) var(--radius-outer)}.all-filters summary:hover,.all-filters[open] summary{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.all-filters summary:hover::after{background-color:rgb(var(--action-contrast))}.all-filters summary{width:100%}.all-filters>.row.row{padding:0 .75rem;width:var(--content);position:relative}.all-filters>.row>.label,.all-filters>.row>.row>.label{font-family:var(--heading);font-weight:var(--fw-h-bold);text-transform:uppercase}.all-filters>.row>.label{width:20%}.all-filters>.row>.row>.label{white-space:nowrap}.all-filters .btn+label,.all-filters button{width:var(--chipchip);min-height:var(--chipchip);padding:0}.all-filters .btn+label,.all-filters button{position:unset}.all-filters .row:has(>.btn:not(:checked)+label:hover) :checked+label .label,.all-filters button .label,.btn+label .label{position:absolute;top:2rem;left:1rem;width:max-content;white-space:nowrap;opacity:0;z-index:var(--z-4)}.all-filters .radio-options.order>.row{position:relative;padding-bottom:2rem}.all-filters .radio-options .row .btn+label .label{bottom:0;top:unset;left:0;height:max-content}.all-filters button:hover .label,.btn+label:hover .label,.btn:checked+label .label{opacity:1}.all-filters .radio-options.order>.row{max-width:49%}.all-filters .radio-options.order{margin-top:1rem}.all-filters .filters select,.all-filters .filters>.row{width:max-content}.search-container:not(.open) .clear-search,.search-container:not(.open) input[type=search]{transform:scaleX(0);transform-origin:left;width:0;padding:0;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.search-container button{padding:.5rem}.search-container .icon{--w:1.5rem}.search-container.open .clear-search,.search-container.open input[type=search]{transform:scaleX(1);transform-origin:left;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.all-filters>.search,.search-container,input[type=search]{width:100%}section.main-actions.main-actions{padding:0;position:absolute;left:var(--offScreen)}.dashboard main>footer{padding:0;margin:0;position:absolute;left:var(--offScreen)}.item .select-item-label{width:100%;height:100%;aspect-ratio:1} |
| | | .replace{margin-left:var(--btn_)!important}.dashboard aside.main.left{bottom:0}.dashboard .qtoggle{left:0;bottom:0;margin:0!important}nav.sidebar{--wrap:nowrap;--align:flex-start;position:fixed;bottom:0;top:var(--btn);z-index:var(--z-4);height:calc(100% - var(--btn));background-color:rgb(var(--base));box-shadow:rgba(var(--base),var(--op-45)) var(--shdw);width:var(--btn);transition:var(--trans-size);overflow:hidden auto;margin-left:0!important;padding-bottom:var(--btn)}nav.sidebar.left{left:0}nav.sidebar.right{right:0}nav.sidebar .icon{--w:var(--chip_);width:var(--w);transition:var(--trans-size)}nav.sidebar.open{width:fit-content;max-width:85vw}nav.sidebar.open .icon{--w:var(--chip)}nav.sidebar ul{height:max-content;width:100%;--gap:0;--dir:column}nav.sidebar li{--justify:center;--wrap:nowrap;--align:flex-start;overflow:hidden;height:max-content}nav.sidebar ul ul{max-height:0;overflow:hidden;transform:scaleY(0);transform-origin:top;transition:var(--trans-base)}nav.sidebar li.open>ul{max-height:max-content;transform:scaleY(1)}nav.sidebar.open li>div{width:100%;padding-right:var(--btn)}nav.sidebar.open li.has-submenu>div{padding-right:0}nav.sidebar.open li.has-submenu>ul{padding-left:var(--chip)}nav.sidebar .title{display:none}nav.sidebar.open .title{display:block;white-space:nowrap}nav.sidebar a{--justify:flex-start}nav.sidebar .a{gap:.5rem;display:flex;width:100%;justify-content:flex-start;align-items:center;min-height:var(--btn);padding:var(--padding)}nav.sidebar .toggle:not(.main){display:none;width:var(--btn);height:var(--chipchip)}nav.sidebar.open .toggle{display:flex}nav.sidebar .toggle.main{position:fixed;left:unset;bottom:0;right:0;width:var(--btn);height:var(--btn);z-index:var(--z-8);box-shadow:rgba(var(--base),var(--op-45)) var(--shdw)}.all-filters{font-size:var(--txt-x-small)}.all-filters[open]{border:2px solid rgb(var(--action-0));padding:0;border-radius:0 0 var(--radius-outer) var(--radius-outer)}.all-filters summary:hover,.all-filters[open] summary{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}.all-filters summary:hover::after{background-color:rgb(var(--action-contrast))}.all-filters summary{width:100%}.all-filters>.row.row{padding:0 .75rem;width:var(--content);position:relative}.all-filters>.row>.label,.all-filters>.row>.row>.label{font-family:var(--heading);font-weight:var(--fw-h-bold);text-transform:uppercase}.all-filters>.row>.label{width:20%}.all-filters>.row>.row>.label{white-space:nowrap}.all-filters .btn+label,.all-filters button{width:var(--chipchip);min-height:var(--chipchip);padding:0}.all-filters .btn+label,.all-filters button{position:unset}.all-filters .row:has(>.btn:not(:checked)+label:hover) :checked+label .label,.all-filters button .label,.btn+label .label{position:absolute;top:2rem;left:1rem;width:max-content;white-space:nowrap;opacity:0;z-index:var(--z-4)}.all-filters .radio-options.order>.row{position:relative;padding-bottom:2rem}.all-filters .radio-options .row .btn+label .label{bottom:0;top:unset;left:0;height:max-content}.all-filters button:hover .label,.btn+label:hover .label,.btn:checked+label .label{opacity:1}.all-filters .radio-options.order>.row{max-width:49%}.all-filters .radio-options.order{margin-top:1rem}.all-filters .filters select,.all-filters .filters>.row{width:max-content}.search-container:not(.open) .clear-search,.search-container:not(.open) input[type=search]{transform:scaleX(0);transform-origin:left;width:0;padding:0;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.search-container button{padding:.5rem}.search-container .icon{--w:1.5rem}.search-container.open .clear-search,.search-container.open input[type=search]{transform:scaleX(1);transform-origin:left;transition:transform var(--trans-base),width var(--trans-base),padding var(--trans-base)}.all-filters>.search,.search-container,input[type=search]{width:100%}section.main-actions.main-actions{padding:0;position:absolute;left:var(--offScreen)}.dashboard main>footer{padding:0;margin:0;position:absolute;left:var(--offScreen)}.item .select-item-label{width:100%;height:100%;aspect-ratio:1}dialog form nav.tabs{--wrap:nowrap!important;--gap:0!important;overflow-x:auto;touch-action:pan-x;position:absolute;top:0;left:0;right:0;max-width:100%;height:var(--chipchip);padding:0;background-color:rgba(var(--base),var(--op-5))}dialog form nav.tabs button{min-height:var(--chipchip);padding:0 1rem}dialog nav.tabs button:hover{background-color:rgb(var(--action-0));color:rgb(var(--action-contrast))}dialog:has(nav.tabs) .wrap{padding-top:3rem}dialog{height:100%!important} |
| | |
| | | // Update button icon |
| | | button.innerHTML = jvbSettings.icons[button.classList.contains('favourited') ? 'heart-filled' : 'heart']; |
| | | |
| | | this.store.setItem(button.dataset.id, { |
| | | window.debouncer.schedule( |
| | | `favourite-${button.dataset.id}`, |
| | | this.postFavourite({ |
| | | user: window.auth.getUser(), |
| | | target_id: button.dataset.id, |
| | | action: action, |
| | | type: button.dataset.type |
| | | }), |
| | | 100 |
| | | ); |
| | | |
| | | } |
| | | |
| | | async postFavourite(args) { |
| | | await window.auth.fetch( |
| | | `${jvbSettings.api}favourites`, |
| | | { |
| | | method: 'POST', |
| | | headers: { |
| | | 'X-Action-Nonce': window.auth.getNonce('favourites') |
| | | }, |
| | | body: args |
| | | } |
| | | ); |
| | | |
| | | if (args.action === 'add') { |
| | | await this.store.setItem(button.dataset.id, { |
| | | target_id: button.dataset.id, |
| | | action: action, |
| | | type: button.dataset.type, |
| | | artist: button.dataset.artist, |
| | | }); |
| | | }).then(()=>{}); |
| | | } else { |
| | | await this.store.delete(button.dataset.id).then(()=>{}); |
| | | } |
| | | } |
| | | |
| | | // async toggleFavourite(itemType, itemId) { |
| | |
| | | (()=>{class t{constructor(){let t=window.jvbStore.register("favourites",{storeName:"favourites",keyPath:"id",endpoint:"favourites",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},indexes:[{name:"content",keyPath:"content"},{name:"listId",keyPath:"listId"}],TTL:36e4,showLoading:!1,filters:{user:window.auth.getUser(),content:"all",order:"desc",orderby:"date",page:1,all:!0}});this.store=t.favourites,this.store.subscribe(((t,e)=>{t}))}toggleFavourite(t){if(!window.auth.getUser())return void(window.location.href=jvbSettings.redirect+"&action=register&type=favourites");t.classList.toggle("favourited");const e=t.classList.contains("favourited")?"add":"remove",o=t.classList.contains("favourited")?`Added ${t.dataset.type} to favourites.`:`Removed ${t.dataset.type} from favourites.`;window.jvbA11y.announce(o),t.innerHTML=jvbSettings.icons[t.classList.contains("favourited")?"heart-filled":"heart"],this.store.setItem(t.dataset.id,{target_id:t.dataset.id,action:e,type:t.dataset.type,artist:t.dataset.artist})}isFavourited(t,e){const o=`${this.userId}_${t}_${e}`;return void 0!==this.store.get(o)}}document.addEventListener("DOMContentLoaded",(function(){window.jvbFavourites=!1,window.auth.subscribe((e=>{"auth-loaded"===e&&(window.jvbFavourites=new t)}))})),window.toggleFavourite=function(t){window.jvbFavourites()?window.jvbFavourites.toggleFavourite(t):console.log("No Favourites Loaded")},window.isFavourited=function(t,e){if(window.jvbFavourites())return window.jvbFavourites.isFavourited(t,e);console.log("No Favourites Loaded")}})(); |
| | | (()=>{class t{constructor(){let t=window.jvbStore.register("favourites",{storeName:"favourites",keyPath:"id",endpoint:"favourites",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},indexes:[{name:"content",keyPath:"content"},{name:"listId",keyPath:"listId"}],TTL:36e4,showLoading:!1,filters:{user:window.auth.getUser(),content:"all",order:"desc",orderby:"date",page:1,all:!0}});this.store=t.favourites,this.store.subscribe(((t,e)=>{t}))}toggleFavourite(t){if(!window.auth.getUser())return void(window.location.href=jvbSettings.redirect+"&action=register&type=favourites");t.classList.toggle("favourited");const e=t.classList.contains("favourited")?"add":"remove",o=t.classList.contains("favourited")?`Added ${t.dataset.type} to favourites.`:`Removed ${t.dataset.type} from favourites.`;window.jvbA11y.announce(o),t.innerHTML=jvbSettings.icons[t.classList.contains("favourited")?"heart-filled":"heart"],window.debouncer.schedule(`favourite-${t.dataset.id}`,this.postFavourite({user:window.auth.getUser(),target_id:t.dataset.id,action:e,type:t.dataset.type}),100)}async postFavourite(t){await window.auth.fetch(`${jvbSettings.api}favourites`,{method:"POST",headers:{"X-Action-Nonce":window.auth.getNonce("favourites")},body:t}),"add"===t.action?await this.store.setItem(button.dataset.id,{target_id:button.dataset.id,action,type:button.dataset.type}).then((()=>{})):await this.store.delete(button.dataset.id).then((()=>{}))}isFavourited(t,e){const o=`${this.userId}_${t}_${e}`;return void 0!==this.store.get(o)}}document.addEventListener("DOMContentLoaded",(function(){window.jvbFavourites=!1,window.auth.subscribe((e=>{"auth-loaded"===e&&(window.jvbFavourites=new t)}))})),window.toggleFavourite=function(t){window.jvbFavourites()?window.jvbFavourites.toggleFavourite(t):console.log("No Favourites Loaded")},window.isFavourited=function(t,e){if(window.jvbFavourites())return window.jvbFavourites.isFavourited(t,e);console.log("No Favourites Loaded")}})(); |
| | |
| | | // Fields and sections |
| | | $this->skeleton->setFields($this->registrar->getFields()); |
| | | |
| | | jvbDump($this->registrar->getSections()); |
| | | $sections = $this->registrar->getSections(); |
| | | if (count($sections) > 1) { |
| | | foreach ($sections as $config) { |
| | | jvbDump($config); |
| | | $this->skeleton->addSection($config['id'], $config); |
| | | $this->skeleton->addSection($config['slug'], $config); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | protected function addDateRanges():array |
| | | { |
| | | return $this->cache->remember( |
| | | return $this->cache->user()->remember( |
| | | 'dateRanges', |
| | | function() { |
| | | $postType = jvbCheckBase($this->content); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Set LIMIT |
| | | * Set pagination |
| | | * |
| | | * @param int $limit Number of records |
| | | * @param int $offset Optional offset |
| | | * @param int $perPage Number of records per page |
| | | * @param int $page Page number (1-based) |
| | | * @return self |
| | | */ |
| | | public function limit(int $limit, int $offset = 0): self |
| | | public function limit(int $perPage, int $page = 1): self |
| | | { |
| | | $this->builder['limit'] = $limit; |
| | | $this->builder['offset'] = $offset; |
| | | $this->builder['per_page'] = $perPage; |
| | | $this->builder['page'] = $page; |
| | | return $this; |
| | | } |
| | | |
| | |
| | | * 'where' => ['user_id' => 1], |
| | | * 'orderby' => 'date_added', |
| | | * 'order' => 'DESC', |
| | | * 'limit' => 20 |
| | | * 'per_page' => 20 |
| | | * ]); |
| | | */ |
| | | public function getMany(array $args = [], string $output = OBJECT): array |
| | | public function getMany(array $args = [], bool $itemsOnly = true, string $output = OBJECT): array |
| | | { |
| | | return $this->cache->remember( |
| | | $items = $this->cache->remember( |
| | | $this->cache->generateKey(array_merge($args, ['output' => $output])), |
| | | function () use ($args, $output) { |
| | | $query = "SELECT * FROM {$this->fullTableName}"; |
| | |
| | | } |
| | | |
| | | // LIMIT |
| | | if (!empty($args['limit'])) { |
| | | $limit = absint($args['limit']); |
| | | $offset = !empty($args['offset']) ? absint($args['offset']) : 0; |
| | | $query .= " LIMIT {$offset}, {$limit}"; |
| | | if (array_key_exists('limit', $args)) { |
| | | error_log('[CustomTable]::getMany deprecated key \'limit\' - use \'per_page\' instead. '.print_r($args, true)); |
| | | $args['per_page'] = $args['limit']; |
| | | } |
| | | if (array_key_exists('offset', $args)) { |
| | | error_log('[CustomTable]::getMany deprecated key \'offset\' - use \'page\' instead. '.print_r($args, true)); |
| | | $args['page'] = $args['offset'] / $args['limit']; |
| | | } |
| | | if (!empty($args['per_page'])) { |
| | | $perPage = absint($args['per_page']); |
| | | $page = !empty($args['page']) ? absint($args['page']) : 1; |
| | | $offset = ($page - 1) * $perPage; |
| | | $query .= " LIMIT {$offset}, {$perPage}"; |
| | | } |
| | | |
| | | if (empty($values)) { |
| | |
| | | } |
| | | ); |
| | | |
| | | if ($itemsOnly) { |
| | | return $items; |
| | | } |
| | | $page = max(1, $args['page'] ?? 1); |
| | | $perPage = $args['per_page']??false; |
| | | $total = $this->count($args['where']); |
| | | return [ |
| | | 'items' => $items, |
| | | 'total' => $total, |
| | | 'has_more' => $perPage && ($page * $perPage) < $total, |
| | | ]; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Get a specific column value from all matches |
| | |
| | | $args['order'] = $order; |
| | | } |
| | | if ($limit) { |
| | | $args['limit'] = $limit; |
| | | $args['per_page'] = $limit; |
| | | } |
| | | |
| | | return array_column($this->getMany($args), $column); |
| | |
| | | 'status' => $accept ? 'accepted' : 'rejected', |
| | | ], $args); |
| | | } |
| | | |
| | | public function getFavourites(array $args = []):array |
| | | { |
| | | return $this->favourites->getMany($args, false); |
| | | } |
| | | public function getLists(array $args = []):array |
| | | { |
| | | return $this->lists->getMany($args, false); |
| | | } |
| | | public function getAvailableLists(array $args = [], bool $include_shared = true):array |
| | | { |
| | | $lists = []; |
| | | $owned = $this->lists->getMany($args, false); |
| | | foreach ($owned['items'] as &$list) { |
| | | $list['item_count'] = $this->listItems->count(['list_id' => $list['id']]); |
| | | } |
| | | |
| | | $lists['owned'] = $owned; |
| | | |
| | | if ($include_shared) { |
| | | $args['where']['status'] = 'accepted'; |
| | | $sharedLists = $this->listShares->getMany($args); |
| | | $shared = []; |
| | | |
| | | foreach ($sharedLists as $share) { |
| | | $sharedList = $this->lists->get(['id' => $share->list_id]); |
| | | if ($sharedList) { |
| | | $sharedList['owner_name'] = jvbGetUsername($sharedList['user_id']); |
| | | $sharedList['item_count'] = $this->listItems->count(['list_id' => $sharedList['id']]); |
| | | $sharedList['permission_type'] = $sharedList->permission_type; |
| | | $shared[] = $sharedList; |
| | | } |
| | | } |
| | | $lists['shared'] = $shared; |
| | | } |
| | | return $lists; |
| | | } |
| | | |
| | | public function userOwnsList(int $list_id, int $user_id):bool |
| | | { |
| | | return $this->lists->count(['id' => $list_id, 'user_id' => $user_id]) > 0; |
| | | } |
| | | |
| | | public function getListDetails(int $list_id, int $user_id, int $page = 1):array |
| | | { |
| | | $list = JVB()->favourites()->getLists(['id' => $list_id, 'user_id' => $user_id]); |
| | | if (!$list) { |
| | | return []; |
| | | } |
| | | $items = $this->listItems->getMany([ |
| | | 'where' => ['list_id' => $list_id], |
| | | 'order_by' => 'added_at', |
| | | 'order' => 'DESC', |
| | | 'per_page' => 25, |
| | | 'page' => $page, |
| | | ]); |
| | | |
| | | $formatted = []; |
| | | |
| | | foreach($items as $item) { |
| | | //See if we can get the actual favourite record first |
| | | if ($item->favourite_id) { |
| | | $fav = $this->favourites->get(['id' => $item->favourite_id]); |
| | | if ($fav) { |
| | | $formatted[] = $fav; |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | //Create a dummy favourite object otherwise |
| | | $formatted[] = (object) [ |
| | | 'type' => $item->item_type, |
| | | 'target_id' => $item->item_id, |
| | | 'created_at'=> $item->added_at |
| | | ]; |
| | | } |
| | | |
| | | $sharedWith = []; |
| | | $is_owner = $this->userOwnsList($list_id, $user_id); |
| | | if ($is_owner) { |
| | | $shares = $this->listShares->getMany([ |
| | | 'where' => ['list_id' => $list_id], |
| | | 'order_by' => 'created_at', |
| | | 'order' => 'DESC' |
| | | ]); |
| | | foreach ($shares as $share_item) { |
| | | $user = [ |
| | | 'email' => $share_item->email, |
| | | 'status' => $share_item->status, |
| | | 'date_added'=> $share_item->created_at, |
| | | ]; |
| | | if ($share_item->status === 'accepted' && $share_item->user_id) { |
| | | $user['name'] = jvbGetUsername($share_item->user_id); |
| | | $user['permission_type'] = $share_item->permission_type; |
| | | } |
| | | $sharedWith[] = $user; |
| | | } |
| | | } |
| | | |
| | | return [ |
| | | 'id' => (int)$list['id'], |
| | | 'name' => $list['name'], |
| | | 'description' => $list['description']??'', |
| | | 'created_at' => $list['created_at'], |
| | | 'is_owner' => $is_owner, |
| | | 'items' => $formatted, |
| | | 'shared_users' => $sharedWith |
| | | ]; |
| | | } |
| | | /*************************************************************** |
| | | * UTILITY METHODS |
| | | **************************************************************/ |
| | |
| | | ]; |
| | | } |
| | | $fields = [ |
| | | 'post_status' => [ |
| | | 'type' => 'radio', |
| | | 'label' => 'Status', |
| | | 'default' => 'draft', |
| | | 'options' => $statuses |
| | | ], |
| | | 'post_thumbnail' => [ |
| | | 'type' => 'upload', |
| | | 'subtype' => 'image', |
| | |
| | | 'label' => 'TLDR', |
| | | 'maxLength' => 158, |
| | | ], |
| | | 'post_status' => [ |
| | | 'type' => 'radio', |
| | | 'label' => 'Status', |
| | | 'default' => 'draft', |
| | | 'options' => $statuses |
| | | ] |
| | | ]; |
| | | |
| | | foreach ($fields as $name => $config) { |
| | |
| | | public function getSections():array |
| | | { |
| | | $allSections = array_map(function($section) { |
| | | return $section->getConfig; |
| | | return $section->getConfig(); |
| | | }, $this->sections); |
| | | |
| | | |
| | | if (!empty($this->sectionOrder)) { |
| | | $allSections['order'] = $this->sectionOrder; |
| | | } |
| | |
| | | foreach ($sections as $s) { |
| | | $section = new Section($s, $this); |
| | | $section->setTitle(ucwords(implode(' ', explode('-', $s)))); |
| | | $sectionFields = array_map(function ($f) { |
| | | return $f['name']; |
| | | }, array_filter($fields, function ($f) use ($s) { |
| | | $sectionFields = array_filter($fields, function ($f) use ($s) { |
| | | $tmp = array_key_exists('section', $f) && !is_null($f['section']) ? $f['section'] : 'main'; |
| | | return $s === $tmp; |
| | | })); |
| | | $section->setFields($sectionFields); |
| | | }); |
| | | $section->setFields(array_keys($sectionFields)); |
| | | $this->sections[$s] = $section; |
| | | } |
| | | } |
| | |
| | | exit; |
| | | } |
| | | |
| | | /** |
| | | * TODO: Extract business logic into a Favourites.php manager class |
| | | */ |
| | | class FavouritesRoutes extends Rest |
| | | { |
| | | protected array $valid_types; |
| | |
| | | ->post([$this, 'handleFavourite']) |
| | | ->args([ |
| | | 'user' => 'integer|required', |
| | | 'id' => 'string|required', |
| | | 'action' => 'string|required|enum:add,remove,toggle,batch,note', |
| | | 'type' => 'string', |
| | | 'target_id' => 'integer', |
| | |
| | | if ($cache_check) { |
| | | return $cache_check; |
| | | } |
| | | |
| | | if (count($args) === 1 || ($request->get_param('include_all') === true)) { |
| | | $result = $this->getAllFavourites($user_id); |
| | | } else { |
| | | $result = $this->cache->remember($key, function() use ($args) { |
| | | return $this->getFilteredFavourites($args); |
| | | }); |
| | | } |
| | | $result = JVB()->favourites()->getFavourites($args); |
| | | $result['items'] = $this->formatItems($result['items']); |
| | | |
| | | return $this->addCacheHeaders(Response::success($result)); |
| | | } |
| | | |
| | | /** |
| | | * Get filtered favourites using CustomTable fluent interface |
| | | */ |
| | | protected function getFilteredFavourites(array $args): array |
| | | { |
| | | try { |
| | | // Build base query |
| | | $query = $this->favourites->where(['user_id' => $args['user']]); |
| | | |
| | | // Add type filter if specified |
| | | if (!empty($args['content']) && $args['content'] !== 'all') { |
| | | $query = $this->favourites->where([ |
| | | 'user_id' => $args['user'], |
| | | 'type' => BASE . $args['content'] |
| | | ]); |
| | | } |
| | | |
| | | // Apply ordering and pagination |
| | | $orderby = in_array($args['orderby'] ?? 'date_added', ['date_added', 'type']) |
| | | ? $args['orderby'] |
| | | : 'date_added'; |
| | | $order = in_array(strtoupper($args['order'] ?? 'DESC'), ['ASC', 'DESC']) |
| | | ? strtoupper($args['order']) |
| | | : 'DESC'; |
| | | |
| | | $favourites = $query |
| | | ->orderBy($orderby, $order) |
| | | ->limit(100, ($args['page'] - 1) * 100) |
| | | ->getResults(); |
| | | |
| | | // Get total count |
| | | $count_query = $this->favourites->where(['user_id' => $args['user']]); |
| | | if (!empty($args['content']) && $args['content'] !== 'all') { |
| | | $count_query->where(['type' => BASE . $args['content']]); |
| | | } |
| | | $total_items = $count_query->countResults(); |
| | | |
| | | return [ |
| | | 'items' => $this->formatItems($favourites), |
| | | 'has_more' => ($args['page'] * 100) < $total_items, |
| | | 'total' => $total_items, |
| | | 'success' => true, |
| | | ]; |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('getFilteredFavourites', [ |
| | | 'error' => $e->getMessage(), |
| | | 'args' => $args |
| | | ]); |
| | | |
| | | return [ |
| | | 'success' => false, |
| | | 'items' => [], |
| | | 'total' => 0, |
| | | 'has_more' => false |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get all user's favourites organized by content type |
| | | */ |
| | | protected function getAllFavourites(int $user_id): array |
| | | { |
| | | return $this->cache->remember($user_id, function() use ($user_id) { |
| | | try { |
| | | $favourites = $this->favourites |
| | | ->where(['user_id' => $user_id]) |
| | | ->getResults(); |
| | | |
| | | $by_type = []; |
| | | foreach ($favourites as $fav) { |
| | | $type = str_replace(BASE, '', $fav->type); |
| | | if (!isset($by_type[$type])) { |
| | | $by_type[$type] = []; |
| | | } |
| | | $by_type[$type][] = (int)$fav->target_id; |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'items' => $by_type, |
| | | 'has_more' => false, |
| | | ]; |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('getAllFavourites', [ |
| | | 'error' => $e->getMessage(), |
| | | 'user_id' => $user_id |
| | | ]); |
| | | |
| | | return [ |
| | | 'success' => false, |
| | | 'items' => [], |
| | | ]; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Handle favourite operations |
| | | */ |
| | | public function handleFavourite(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $user_id = absint($request->get_param('user')); |
| | | $operation_id = sanitize_text_field($request->get_param('id')); |
| | | $action = sanitize_text_field($request->get_param('action')); |
| | | $params = $request->get_params(); |
| | | $user_id = absint($params['user']??0); |
| | | |
| | | if (!$this->userCheck($user_id)) { |
| | | return $this->unauthorized(); |
| | | } |
| | | $action = strtolower(sanitize_text_field($params['action'])); |
| | | $action = in_array($action, ['add', 'remove']) ? $action : false; |
| | | if (!$action) { |
| | | return $this->error('Invalid favourite action'); |
| | | } |
| | | $target_id = absint($params['target_id']??0); |
| | | if ($target_id === 0) { |
| | | return $this->error('Invalid target id'); |
| | | } |
| | | |
| | | $data = [ |
| | | 'action' => $action, |
| | | 'type' => sanitize_text_field($request->get_param('type') ?? ''), |
| | | 'target_id' => absint($request->get_param('target_id') ?? 0), |
| | | 'items' => $request->get_param('items') ?? [], |
| | | 'notes' => sanitize_textarea_field($request->get_param('notes') ?? ''), |
| | | ]; |
| | | $type = sanitize_text_field($params['type']??''); |
| | | if (empty($type)) { |
| | | return $this->error('No type provided'); |
| | | } |
| | | |
| | | JVB()->queue()->queueOperation( |
| | | 'favourite_' . $action, |
| | | $result = JVB()->favourites()->toggleFavourite( |
| | | $action === 'add', |
| | | $user_id, |
| | | $data, |
| | | [ |
| | | 'operation_id' => $operation_id, |
| | | 'priority' => 'high', |
| | | ] |
| | | $target_id, |
| | | $type |
| | | ); |
| | | |
| | | return $this->queued($operation_id); |
| | | if ($result) { |
| | | return $this->success(); |
| | | } |
| | | return $this->error('Something went wrong'); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function getLists(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $user_id = absint($request->get_param('user')); |
| | | $params = $request->get_params(); |
| | | $user_id = absint($params['user']); |
| | | |
| | | if (!$this->userCheck($user_id)) { |
| | | return $this->unauthorized(); |
| | | } |
| | | |
| | | $params = ['user' => $user_id]; |
| | | if ($request->get_param('id')) { |
| | | $params['list'] = sanitize_text_field($request->get_param('id')); |
| | | $args = $this->buildParams($request); |
| | | $args['per_page'] = 20; |
| | | $listId = $request->get_param('id'); |
| | | if (!empty($listId)) { |
| | | $args['where']['id'] = sanitize_text_field($listId); |
| | | } |
| | | |
| | | $key = $this->listsCache->generateKey($params); |
| | | $key = $this->listsCache->generateKey($args); |
| | | |
| | | // Check cache headers |
| | | $cache_check = $this->checkHeaders($request, $key); |
| | |
| | | return $cache_check; |
| | | } |
| | | |
| | | $list_id = $request->get_param('id'); |
| | | $response = $list_id |
| | | ? $this->getListDetails($list_id, $user_id) |
| | | : $this->getAvailableLists($user_id); |
| | | $includeShares = !empty($request->get_param('include_shares')); |
| | | |
| | | $response = !empty($listId) |
| | | ? JVB()->favourites()->getListDetails($listId, $user_id) |
| | | : JVB()->favourites()->getAvailableLists($args, $includeShares); |
| | | |
| | | return $this->addCacheHeaders(Response::success($response)); |
| | | } |
| | | |
| | | /** |
| | | * Get lists available to a user using CustomTable |
| | | */ |
| | | protected function getAvailableLists(int $user_id, bool $include_shared = true): array |
| | | { |
| | | if (!$this->checkUser($user_id)) { |
| | | return []; |
| | | } |
| | | |
| | | $cache = $include_shared ? $this->sharedListsCache : $this->listsCache; |
| | | |
| | | return $cache->remember($user_id, function() use ($user_id, $include_shared) { |
| | | try { |
| | | // Get owned lists |
| | | $owned = $this->lists |
| | | ->where(['user_id' => $user_id]) |
| | | ->orderBy('created_at', 'DESC') |
| | | ->getResults(ARRAY_A); |
| | | |
| | | // Add item counts |
| | | foreach ($owned as &$list) { |
| | | $list['item_count'] = $this->listItems |
| | | ->where(['list_id' => $list['id']]) |
| | | ->countResults(); |
| | | $list['is_owner'] = true; |
| | | $list['is_shared'] = false; |
| | | } |
| | | |
| | | if (!$include_shared) { |
| | | return [ |
| | | 'success' => true, |
| | | 'lists' => $owned |
| | | ]; |
| | | } |
| | | |
| | | // Get shared lists |
| | | $shares = $this->listShares |
| | | ->where(['user_id' => $user_id, 'status' => 'accepted']) |
| | | ->getResults(); |
| | | |
| | | $shared_lists = []; |
| | | foreach ($shares as $share) { |
| | | $list = $this->lists |
| | | ->where(['id' => $share->list_id]) |
| | | ->first(ARRAY_A); |
| | | |
| | | if ($list) { |
| | | $owner = get_userdata($list['user_id']); |
| | | $list['owner_name'] = $owner ? $owner->display_name : 'Unknown'; |
| | | $list['item_count'] = $this->listItems |
| | | ->where(['list_id' => $list['id']]) |
| | | ->countResults(); |
| | | $list['permission_type'] = $share->permission_type; |
| | | $list['is_owner'] = false; |
| | | $list['is_shared'] = true; |
| | | |
| | | $shared_lists[] = $list; |
| | | } |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'lists' => [ |
| | | 'owned' => $owned, |
| | | 'shared' => $shared_lists |
| | | ] |
| | | ]; |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('getAvailableLists', [ |
| | | 'error' => $e->getMessage(), |
| | | 'user_id' => $user_id |
| | | ]); |
| | | |
| | | return []; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * TODO: Done until here |
| | | * Get favourite counts by type |
| | | */ |
| | | public function getFavouriteCounts(WP_REST_Request $request): WP_REST_Response |
| | |
| | | protected function buildParams(WP_REST_Request $request): array |
| | | { |
| | | $data = $request->get_params(); |
| | | $args = ['user' => absint($data['user'])]; |
| | | |
| | | if (!array_key_exists('page', $data)) { |
| | | return $args; |
| | | $where = ['user_id' => absint($data['user'])]; |
| | | if (!empty($data['content']) && $data['content'] !== 'all') { |
| | | $where['type'] = BASE . $data['content']; |
| | | } |
| | | |
| | | $args = array_merge($args, [ |
| | | 'page' => max(1, absint($data['page'] ?? 1)), |
| | | 'content' => Registrar::getInstance($data['content']) ? $data['content'] : 'all', |
| | | ]); |
| | | $page = max(1, absint($data['page'] ?? 1)); |
| | | $perPage = 250; |
| | | |
| | | return $this->applyOrderFilters($args, $data); |
| | | return [ |
| | | 'where' => $where, |
| | | 'orderby' => sanitize_text_field($data['orderby'] ?? 'created_at'), |
| | | 'order' => sanitize_text_field($data['order'] ?? 'DESC'), |
| | | 'per_page' => $perPage, |
| | | 'page' => $page |
| | | ]; |
| | | } |
| | | |
| | | |
| | |
| | | */ |
| | | protected function getListDetails(int $list_id, int $user_id): array |
| | | { |
| | | $key = "list_{$list_id}_user_{$user_id}"; |
| | | |
| | | return $this->listsCache->remember($key, function () use ($list_id, $user_id) { |
| | | try { |
| | | // Check access - either owner or has share |
| | | $is_owner = $this->lists->where([ |
| | | 'id' => $list_id, |
| | | 'user_id' => $user_id |
| | | ])->existsInQuery(); |
| | | $is_owner = JVB()->favourites()->userOwnsList($list_id, $user_id); |
| | | $is_shared = JVB()->favourites()->userCanViewList($list_id, $user_id); |
| | | |
| | | $share = null; |
| | | if (!$is_owner) { |
| | | $share = $this->listShares->where([ |
| | | 'list_id' => $list_id, |
| | | 'user_id' => $user_id, |
| | | 'status' => 'accepted' |
| | | ])->first(); |
| | | } |
| | | |
| | | if (!$is_owner && !$share) { |
| | | if (!$is_owner && !$is_shared) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'You do not have access to this list' |
| | | 'message' => 'You do not have access to this list.' |
| | | ]; |
| | | } |
| | | |
| | | // Get list details |
| | | $list = $this->lists->where(['id' => $list_id])->first(ARRAY_A); |
| | | if (!$list) { |
| | | $list = JVB()->favourites()->getListDetails($list_id, $user_id); |
| | | |
| | | if (empty($list)) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'List not found' |
| | | ]; |
| | | } |
| | | |
| | | |
| | | |
| | | $key = "list_{$list_id}_user_{$user_id}"; |
| | | |
| | | return $this->listsCache->remember($key, function () use ($list_id, $user_id) { |
| | | try { |
| | | |
| | | |
| | | |
| | | // Get list items |
| | | $items = $this->listItems |
| | | ->where(['list_id' => $list_id]) |
| | |
| | | <input type="hidden" name="content" value="<?=$this->dataType?>" /> |
| | | <div class="fields"> |
| | | <?php |
| | | if (!empty($this->statuses)) { |
| | | if (empty($this->sections) && !empty($this->statuses)) { |
| | | echo Form::render('post_status', '', $this->getStatusFieldConfig('edit-')); |
| | | } |
| | | |
| | |
| | | 'icon' => $config['icon'] |
| | | ]; |
| | | } |
| | | |
| | | $tabs[$slug] = array_merge([ |
| | | 'title' => $config['label'], |
| | | 'title' => $config['title'], |
| | | 'content' => '', |
| | | 'description' => $config['description']??'', |
| | | ], $section); |
| | |
| | | foreach ($first as $f) { |
| | | if (array_key_exists($f, $fields)) { |
| | | if ($tabs) { |
| | | $tabs['basic']['content'] .= Form::render($f, '', $fields[$f]); |
| | | $tabs['main']['content'] .= Form::render($f, '', $fields[$f]); |
| | | } else { |
| | | echo Form::render($f, '', $fields[$f]); |
| | | } |
| | |
| | | |
| | | $fields = $this->nonTimelineFields; |
| | | } |
| | | |
| | | foreach ($fields as $n => $config) { |
| | | if (in_array($config['type'], ['taxonomy', 'selector'])) { |
| | | $config = array_merge($config, $this->taxConfig($config['taxonomy'], $config['label'])); |
| | | } |
| | | if ($tabs) { |
| | | $section = (array_key_exists('section', $config)) ? $config['section'] : 'basic'; |
| | | $section = (array_key_exists('section', $config)) && !empty($config['section']) ? $config['section'] : 'main'; |
| | | $tabs[$section]['content'] .= Form::render($n, '', $config); |
| | | } else { |
| | | echo Form::render($n, '', $config); |