From f8e75e847d2c1e276081f0733c7be232e96ba342 Mon Sep 17 00:00:00 2001 From: Joey Kimsey Date: Sat, 21 Dec 2024 16:26:05 -0500 Subject: [PATCH] mvp --- app/classes/preferences.php | 76 ++++- app/css/main-dark.css | 5 + app/css/main.css | 35 +- .../bookmarks/controllers/bookmarks.php | 101 ++++-- app/plugins/bookmarks/forms.php | 12 + app/plugins/bookmarks/js/bookmarks.js | 323 ++++++++++-------- .../bookmarks/models/bookmark_dashboards.php | 35 +- app/plugins/bookmarks/models/bookmarks.php | 44 +-- app/plugins/bookmarks/plugin.php | 14 +- .../bookmarks/views/bookmarks/create.html | 2 +- .../bookmarks/views/bookmarks/edit.html | 2 +- .../bookmarks/views/bookmarks/list.html | 4 +- .../bookmarks/views/bookmarks/unsorted.html | 4 +- .../views/components/bookmarkListPanel.html | 11 +- .../views/components/bookmarkListRows.html | 4 +- .../views/components/publicListRows.html | 2 +- .../views/components/shareListPanel.html | 6 +- .../views/components/shareListRows.html | 2 +- .../views/dashboards/bookmarkRows.html | 2 +- .../bookmarks/views/dashboards/create.html | 2 +- .../views/dashboards/dashOptions.html | 4 +- .../bookmarks/views/dashboards/edit.html | 2 +- .../views/dashboards/folderPanels.html | 11 +- .../bookmarks/views/dashboards/list.html | 6 +- .../bookmarks/views/dashboards/view.html | 6 +- app/plugins/bookmarks/views/export.html | 25 +- .../bookmarks/views/extensions/brave.html | 2 +- .../bookmarks/views/extensions/chrome.html | 2 +- .../bookmarks/views/extensions/edge.html | 2 +- .../bookmarks/views/extensions/firefox.html | 2 +- .../bookmarks/views/extensions/index.html | 10 +- .../bookmarks/views/extensions/opera.html | 2 +- .../bookmarks/views/folders/create.html | 2 +- app/plugins/bookmarks/views/folders/edit.html | 2 +- app/plugins/bookmarks/views/folders/list.html | 4 +- app/plugins/bookmarks/views/folders/view.html | 2 +- app/plugins/bookmarks/views/import.html | 2 +- .../bookmarks/views/nav/viewOptions.html | 2 +- .../members/controllers/admin/members.php | 37 +- app/plugins/members/controllers/member.php | 26 +- .../members/models/membership_products.php | 10 +- app/plugins/members/plugin.php | 152 ++++++--- app/plugins/members/views/admin/orphans.html | 35 ++ .../members/views/admin/products/create.html | 2 +- app/plugins/members/views/admin/scripts.html | 13 + app/plugins/members/views/admin/webhooks.html | 51 ++- app/plugins/members/views/cancel.html | 27 +- .../views/{landing1.html => landing.html} | 14 +- app/plugins/members/views/manage.html | 13 +- app/plugins/members/views/members.html | 3 +- app/plugins/members/views/pause.html | 27 +- .../members/views/paymentcanceled.html | 16 +- .../members/views/paymentcomplete.html | 17 +- app/plugins/members/views/upgrade.html | 6 +- .../members/views/upgradeCompleted.html | 13 + app/views/index.html | 4 +- app/views/user_cp/email_change.html | 2 +- app/views/user_cp/password_change.html | 2 +- app/views/user_cp/settings.html | 4 +- 59 files changed, 861 insertions(+), 387 deletions(-) create mode 100644 app/plugins/members/views/admin/orphans.html create mode 100644 app/plugins/members/views/admin/scripts.html rename app/plugins/members/views/{landing1.html => landing.html} (88%) create mode 100644 app/plugins/members/views/upgradeCompleted.html diff --git a/app/classes/preferences.php b/app/classes/preferences.php index b059ffa..958f0bc 100644 --- a/app/classes/preferences.php +++ b/app/classes/preferences.php @@ -13,6 +13,7 @@ namespace TheTempusProject\Classes; use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Forms; +use TheTempusProject\Houdini\Classes\Template; use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Upload; @@ -186,17 +187,90 @@ class Preferences { } public function getFormHtml( $populated = [] ) { + // dv( self::$preferences ); $form = ''; + // Added so i can force some sort of ordering + $inputTypes = [ + 'file' => [], + 'select' => [], + 'timezone' => [], + 'checkbox' => [], + 'switch' => [], + ]; foreach ( self::$preferences as $name => $details ) { $tempPrefsArray = $this->normalizePreferenceArray( $name, $details ); if ( isset( $populated[ $name ] ) ) { $tempPrefsArray['default'] = $populated[$name]; } - $form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] ); + // $form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] ); + if ( $tempPrefsArray['type'] == 'checkbox' ) { + $tempPrefsArray['type'] = 'switch'; + } + $inputTypes[ $tempPrefsArray['type'] ][] = self::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] ); + } + foreach ( $inputTypes as $skip => $items ) { + $form .= implode( ' ', $items ); } return $form; } + public static function getFormFieldHtml( $fieldname, $fieldTitle, $type, $defaultValue = '', $options = null ) { + $html = ''; + switch ( $type ) { + case 'radio': + case 'bool': + case 'boolean': + $fieldHtml = Forms::getRadioHtml( $fieldname, [ 'true', 'false' ], $defaultValue ); + break; + case 'select': + $fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue ); + break; + case 'customSelect': + if ( empty( $options ) ) { + $options = '{' . $fieldname . '-options}'; + } + $fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue ); + break; + case 'block': + $fieldHtml = Forms::getTextBlockHtml( $fieldname, $defaultValue ); + break; + case 'text': + case 'url': + $fieldHtml = Forms::getTextHtml( $fieldname, $defaultValue ); + break; + case 'checkbox': + $fieldHtml = Forms::getCheckboxHtml( $fieldname, $defaultValue ); + break; + case 'switch': + $fieldHtml = Forms::getSwitchHtml( $fieldname, $defaultValue ); + break; + case 'timezone': + $fieldHtml = Forms::getTimezoneHtml( $defaultValue ); + break; + case 'file': + $fieldHtml = Forms::getFileHtml( $fieldname ); + break; + default: + Debug::error( "unknown field type: $type" ); + break; + } + + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= $fieldHtml; + $html .= '
'; + if ( 'file' === $type ) { + $html .= '
'; + $html .= '

Current Image

'; + $html .= '
'; + $html .= 'User Avatar'; + $html .= '
'; + } + $html .= '
'; + return Template::parse( $html ); + } + public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) { $prefsArray = []; foreach ( self::$preferences as $name => $details ) { diff --git a/app/css/main-dark.css b/app/css/main-dark.css index 226a12b..a13f078 100644 --- a/app/css/main-dark.css +++ b/app/css/main-dark.css @@ -135,3 +135,8 @@ body { .text-shadow-3 { text-shadow: 0 .5rem 1.5rem rgba(255, 255, 255, .25); } + +.form-control { + background-color: #1f1f1f; + color: #e0e0e0; +} \ No newline at end of file diff --git a/app/css/main.css b/app/css/main.css index 38b2ee5..63f1300 100644 --- a/app/css/main.css +++ b/app/css/main.css @@ -317,31 +317,54 @@ body { a.atb-green:hover { color: #1b947f; } +button.atb-green:hover { + color: #1b947f; +} + + + + +.atb-green-outline-only { + border-color: #85bd3e; +} +a.atb-green-outline-only:hover { + border-color: #85bd3e; +} +button.atb-green-outline-only:hover { + border-color: #85bd3e; +} + + .atb-green-outline { color: #85bd3e; border-color: #85bd3e; } -.atb-green-outline-only { - border-color: #85bd3e; -} a.atb-green-outline:hover { color: #1b947f; border-color: #1b947f; } +button.atb-green-outline:hover { + color: #1b947f; + border-color: #1b947f; +} + .atb-green-bg { color: #fff; background-color: #85bd3e; border-color: #85bd3e; } - a.atb-green-bg:hover { background-color: #1b947f; border-color: #1b947f; - /* background-color: #44a466; */ - /* background-color: #3fa269; */ } +button.atb-green-bg:hover { + background-color: #1b947f; + border-color: #1b947f; +} + + .bookmark-card.dragging { opacity: 0.7; diff --git a/app/plugins/bookmarks/controllers/bookmarks.php b/app/plugins/bookmarks/controllers/bookmarks.php index 1b9c12f..c4c36f6 100644 --- a/app/plugins/bookmarks/controllers/bookmarks.php +++ b/app/plugins/bookmarks/controllers/bookmarks.php @@ -71,6 +71,7 @@ class Bookmarks extends Controller { } public function index() { + self::$title = 'Manage Bookmarks - {SITENAME}'; if ( Input::exists('submit') ) { $prefs = new Preferences; $user = new User; @@ -116,6 +117,7 @@ class Bookmarks extends Controller { } public function unsorted() { + self::$title = 'Unsorted Bookmarks - {SITENAME}'; $bookmarks = self::$bookmarks->noFolder(); Views::view( 'bookmarks.bookmarks.unsorted', $bookmarks ); } @@ -147,6 +149,7 @@ class Bookmarks extends Controller { } public function createBookmark( $id = null ) { + self::$title = 'Add Bookmark - {SITENAME}'; $folderID = Input::get('folder_id') ? Input::get('folder_id') : $id; $folderID = Input::post('folder_id') ? Input::post('folder_id') : $id; $this->setFolderSelect( $folderID ); @@ -182,6 +185,7 @@ class Bookmarks extends Controller { } public function editBookmark( $id = null ) { + self::$title = 'Edit Bookmark - {SITENAME}'; $folderID = Input::exists('folder_id') ? Input::post('folder_id') : ''; $bookmark = self::$bookmarks->findById( $id ); @@ -263,6 +267,7 @@ class Bookmarks extends Controller { } public function createFolder( $id = 0 ) { + self::$title = 'Create Folder - {SITENAME}'; $folderID = Input::exists('folder_id') ? Input::post('folder_id') : $id; $folders = self::$folders->simpleByUser(); if ( ! empty( $folders ) ) { @@ -287,6 +292,7 @@ class Bookmarks extends Controller { } public function editFolder( $id = null ) { + self::$title = 'Edit Folder - {SITENAME}'; $folder = self::$folders->findById( $id ); if ( $folder == false ) { @@ -345,6 +351,11 @@ class Bookmarks extends Controller { * Dashboards */ public function addDash() { + self::$title = 'Add Dashboard - {SITENAME}'; + if ( !App::$isMember ) { + Issues::add( 'notice', 'You must have an active membership to add dashboards.' ); + return $this->index(); + } $folders = self::$folders->byUser() ?? []; if ( !empty( $folders ) ) { @@ -387,6 +398,11 @@ class Bookmarks extends Controller { } public function editDash( $id = null ) { + self::$title = 'Edit Dashboard - {SITENAME}'; + if ( !App::$isMember ) { + Issues::add( 'notice', 'You must have an active membership to edit dashboards.' ); + return $this->index(); + } $dash = self::$dashboards->findById( $id ); if ( $dash == false ) { @@ -449,6 +465,10 @@ class Bookmarks extends Controller { } public function deleteDash( $id = null ) { + if ( !App::$isMember ) { + Issues::add( 'notice', 'You must have an active membership to delete dashboards.' ); + return $this->index(); + } $dash = self::$dashboards->findById( $id ); if ( $dash == false ) { Issues::add( 'error', 'Unknown Dashboard' ); @@ -468,6 +488,11 @@ class Bookmarks extends Controller { } public function dashboard( $uuid = null ) { + self::$title = 'Bookmark Dashboard - {SITENAME}'; + if ( !App::$isMember ) { + Issues::add( 'notice', 'You must have an active membership to view dashboards.' ); + return $this->index(); + } $dash = self::$dashboards->findByUuid( $uuid ); if ( $dash == false ) { return $this->dashboards(); @@ -476,6 +501,32 @@ class Bookmarks extends Controller { Issues::add( 'error', 'You do not have permission to view this dash.' ); return $this->dashboards(); } + if ( Input::exists( 'submit' ) ) { + if ( Forms::check( 'updateDashboard' ) ) { + $filters = ''; + $folders = ''; + + if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) { + $filters = implode( ',', Input::post('link_filter') ); + } + + if ( ! empty( Input::post('link_order') ) ) { + $folders = Input::post('link_order'); + } + + $result = self::$dashboards->updateDash( $dash->ID, $filters, $folders ); + + if ( !$result ) { + Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] ); + } else { + Issues::add( 'success', 'Your dashboard has been saved.' ); + } + } else { + Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] ); + } + unset( $_POST ); + } + $dash = self::$dashboards->findByUuid( $uuid ); $foldersArray = []; if ( ! empty( $dash->link_order ) ) { @@ -504,12 +555,19 @@ class Bookmarks extends Controller { if ( ! empty( $dash->saved_prefs ) ) { $this->setDashToggles( explode( ',', $dash->saved_prefs ) ); + } else { + $this->setDashToggles( [] ); } return Views::view( 'bookmarks.dashboards.view', $dash ); } public function dashboards() { + self::$title = 'Bookmark Dashboards - {SITENAME}'; + if ( !App::$isMember ) { + Issues::add( 'notice', 'You must have an active membership to manage dashboards.' ); + return $this->index(); + } $dashboards = self::$dashboards->byUser(); return Views::view( 'bookmarks.dashboards.list', $dashboards ); } @@ -517,7 +575,7 @@ class Bookmarks extends Controller { /** * Functionality */ - public function publish( $id = null ) { + public function publish( $id = null ) { $bookmark = self::$bookmarks->findById( $id ); if ( $bookmark == false ) { Session::flash( 'error', 'Bookmark not found.' ); @@ -627,6 +685,7 @@ class Bookmarks extends Controller { } public function import() { + self::$title = 'Bookmark Import - {SITENAME}'; if ( !App::$isMember ) { Issues::add( 'notice', 'You must have an active membership to import bookmarks.' ); return $this->index(); @@ -641,50 +700,44 @@ class Bookmarks extends Controller { return Views::view( 'bookmarks.import' ); } - if (isset($_FILES['bookmark_file'])) { + if ( isset( $_FILES['bookmark_file'] ) ) { $file = $_FILES['bookmark_file']; - - // Check file size + if ($file['size'] > 1024 * 1024) { // 1024 KB = 1 MB - die('The file is too large. Maximum size is 1 MB.'); + Issues::add( 'error', 'There is a 1 meg limit on bookmark imports at this time.' ); + return Views::view( 'bookmarks.import' ); } - - // Check file extension + $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if ($fileExtension !== 'html') { - die('Invalid file type. Only .html files are allowed.'); + Issues::add( 'error', 'Invalid file type. Only .html files are allowed.' ); + return Views::view( 'bookmarks.import' ); } - - // Proceed with file processing + $fileContent = file_get_contents($file['tmp_name']); } else { - die('No file was uploaded.'); + Issues::add( 'error', 'No Import detected' ); + return Views::view( 'bookmarks.import' ); } $out = $this->parseBookmarks($fileContent); - $date = 'today'; + $date = date('F j, Y'); $description = 'Imported on ' . $date . ' from file: ' . $file['name']; $importFolder = self::$folders->create( 'New Import', 0, $description ); - // $importFolder = 1; - // echo 'make import folder: ' . PHP_EOL; foreach ($out as $folder => $bookmarks) { - // echo 'make folder: ' . $folder . PHP_EOL; $currentFolder = self::$folders->create( $folder, $importFolder, $description ); foreach ($bookmarks as $index => $bookmark) { - // echo 'make folder: ' . $bookmark['url'] . PHP_EOL; self::$bookmarks->create( $bookmark['name'], $bookmark['url'], $currentFolder); } } Session::flash( 'success', 'Your Bookmark has been created.' ); Redirect::to( 'bookmarks/bookmarks/'. $importFolder ); - // echo ''; - // exit; - // dv ( $out ); } public function export() { + self::$title = 'Bookmark Export - {SITENAME}'; if ( !App::$isMember ) { Issues::add( 'notice', 'You must have an active membership to export bookmarks.' ); return $this->index(); @@ -720,7 +773,11 @@ class Bookmarks extends Controller { Session::flash( 'error', 'Folder not found.' ); return Redirect::to( 'bookmarks/index' ); } - $links = self::$bookmarks->byFolder( $folder->ID ); + + $hiddenExcluded = ! ( Input::post('hiddenIncluded') ?? false ); + $archivedExcluded = ! ( Input::post('archivedIncluded') ?? false ); + + $links = self::$bookmarks->byFolder( $folder->ID, $hiddenExcluded, $archivedExcluded ); $htmlDoc .= $this->exportFolder( $folder->title, $folder->createdAt, $folder->createdAt, $links ); } $htmlDoc .= '

' . PHP_EOL; @@ -774,6 +831,10 @@ class Bookmarks extends Controller { return Views::view( 'bookmarks.share', $panelArray ); } + + /** + * Private + */ private function exportFolder( $title, $editedAt, $createdAt, $links ) { $htmlDoc = '

'.$title.'

' . PHP_EOL; $htmlDoc .= '

' . PHP_EOL; diff --git a/app/plugins/bookmarks/forms.php b/app/plugins/bookmarks/forms.php index ce89547..a76a903 100644 --- a/app/plugins/bookmarks/forms.php +++ b/app/plugins/bookmarks/forms.php @@ -29,6 +29,7 @@ class BookmarksForms extends Forms { self::addHandler( 'exportBookmarks', __CLASS__, 'exportBookmarks' ); self::addHandler( 'createDashboard', __CLASS__, 'createDashboard' ); self::addHandler( 'editDashboard', __CLASS__, 'editDashboard' ); + self::addHandler( 'updateDashboard', __CLASS__, 'updateDashboard' ); } public static function createBookmark() { @@ -190,6 +191,17 @@ class BookmarksForms extends Forms { return true; } + public static function updateDashboard() { + if ( ! Input::exists( 'submit' ) ) { + return false; + } + if ( !self::token() ) { + Check::addUserError( 'There was an issue with your request.' ); + return false; + } + return true; + } + public static function editDashboard() { if ( ! Input::exists( 'submit' ) ) { return false; diff --git a/app/plugins/bookmarks/js/bookmarks.js b/app/plugins/bookmarks/js/bookmarks.js index c3f2856..cf78aab 100644 --- a/app/plugins/bookmarks/js/bookmarks.js +++ b/app/plugins/bookmarks/js/bookmarks.js @@ -1,30 +1,9 @@ +let masonryInstance; document.addEventListener('DOMContentLoaded', () => { // Handle all bootstrap popover inits const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) - const masonryContainer = document.querySelector('[data-masonry]'); - if ( ! masonryContainer ) { - return; - } - const masonryInstance = new Masonry(masonryContainer, { - columnHeight: '.accordion', - percentPosition: true - }); - const updateMasonryLayout = () => masonryInstance.layout(); - - masonryContainer.addEventListener('hidden.bs.collapse', updateMasonryLayout); - masonryContainer.addEventListener('shown.bs.collapse', updateMasonryLayout); - - const observer = new MutationObserver(() => { - updateMasonryLayout(); - }); - - // Observe all cards for changes in the DOM - document.querySelectorAll('.card').forEach((card) => { - observer.observe(card, { childList: true, subtree: true }); - }); - // Initialize all toggle functions toggleVisibility('editModeSwitch', 'edit-mode'); toggleVisibility('showArchivedSwitch', 'link-archived'); @@ -39,6 +18,119 @@ document.addEventListener('DOMContentLoaded', () => { toggleVisibility('dashShowHiddenSwitch', 'link-hidden'); toggleVisibility('dashAddButtonSwitch', 'btn-addlink'); toggleVisibility('dashShareButtonSwitch', 'btn-share'); + + // Retrieve the list of collapsed folderCard IDs from local storage + const collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedFolders' )) || []; + + // Collapse the elements stored in local storage when the page loads + collapsedFolders.forEach((folderId) => { + const collapseElement = document.querySelector(`#Collapse${folderId}`); + if (collapseElement) { + collapseElement.classList.remove('show'); + } + }); + + // Add event listeners to track expand and collapse actions + document.querySelectorAll('.accordion-item').forEach((item) => { + const folderCardId = item.closest('.bookmark-card')?.id?.replace('folderCard', ''); + + if ( ! folderCardId ) return; + + const collapseElement = item.querySelector('.collapse'); + + // Listen for collapse and expand events + collapseElement.addEventListener('hidden.bs.collapse', () => { + // Add the folderCard ID to local storage when collapsed + if (!collapsedFolders.includes(folderCardId)) { + collapsedFolders.push(folderCardId); + localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders)); + } + }); + + collapseElement.addEventListener('shown.bs.collapse', () => { + // Remove the folderCard ID from local storage when expanded + const index = collapsedFolders.indexOf(folderCardId); + if (index > -1) { + collapsedFolders.splice(index, 1); + localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders)); + } + }); + }); + + const masonryContainer = document.getElementById("bookmarkSort"); + + if ( ! masonryContainer ) { + return; + } + + if ( localStorage.getItem('manageFolderOrder') ) { + loadDashLinkOrder(); + } + + masonryInstance = new Masonry(masonryContainer, { + columnHeight: '.accordion', + itemSelector: ".bookmark-card", + percentPosition: false + }); + + const test = () => masonryInstance.layout(); + + masonryContainer.addEventListener('hidden.bs.collapse', test ); + masonryContainer.addEventListener('shown.bs.collapse', test ); + + // Select all folder-raise and folder-lower buttons + + const raiseButtons = document.querySelectorAll(".folder-raise"); + const lowerButtons = document.querySelectorAll(".folder-lower"); + + // Function to move the folderCard up + function moveUp(event) { + const folderCard = event.target.closest(".bookmark-card"); + const bookmarkSort = document.getElementById("bookmarkSort"); + const orderId = document.getElementById("dashLinkOrder"); + + if (folderCard) { + const previousSibling = folderCard.previousElementSibling; + if (previousSibling) { + // Remove and reinsert the element before the previous sibling + bookmarkSort.removeChild(folderCard); + bookmarkSort.insertBefore(folderCard, previousSibling); + masonryInstance.reloadItems(); + masonryInstance.layout(); + updateDashLinkOrder(); + } + } + } + + // Function to move the folderCard down + function moveDown(event) { + const folderCard = event.target.closest(".bookmark-card"); + const bookmarkSort = document.getElementById("bookmarkSort"); + + if (folderCard) { + const nextSibling = folderCard.nextElementSibling; + if (nextSibling) { + // Remove and reinsert the element after the next sibling + bookmarkSort.removeChild(folderCard); + bookmarkSort.insertBefore(folderCard, nextSibling.nextSibling); + masonryInstance.reloadItems(); + masonryInstance.layout(); + updateDashLinkOrder(); + } + } + } + + // Add event listeners to the raise buttons + raiseButtons.forEach(button => { + button.addEventListener("click", moveUp); + }); + + // Add event listeners to the lower buttons + lowerButtons.forEach(button => { + button.addEventListener("click", moveDown); + }); + // after hiding/showing elements + masonryInstance.layout(); }); // Function to handle showing or hiding elements based on the checkbox state @@ -52,143 +144,72 @@ function toggleVisibility(switchId, className) { // Listen for changes to the checkbox switchElement.addEventListener('change', () => { - if (switchElement.checked) { - elementsToToggle.forEach(element => { - element.style.display = ''; // Show the element (default display) - }); - } else { - elementsToToggle.forEach(element => { - element.style.display = 'none'; // Hide the element - }); - } + if (switchElement.checked) { + elementsToToggle.forEach(element => { + element.style.display = ''; + }); + } else { + elementsToToggle.forEach(element => { + element.style.display = 'none'; + }); + } + if (typeof masonryInstance !== 'undefined') { + masonryInstance.reloadItems(); + masonryInstance.layout() + return; + } }); - + // Trigger the toggle initially to set the correct visibility on page load switchElement.dispatchEvent(new Event('change')); } -document.addEventListener('DOMContentLoaded', function () { - const bookmarkSort = document.getElementById('bookmarkSort'); - if ( ! bookmarkSort ) { - return; + +function updateDashLinkOrder() { + const bookmarkCards = document.querySelectorAll("#bookmarkSort .bookmark-card"); + const ids = Array.from( bookmarkCards ).map( card => { + const match = card.id.match(/folderCard(\d+)/); + return match ? match[1] : null; + }).filter( id => id !== null ); + + const dashLinkOrder = document.getElementById("dashLinkOrder"); + if (dashLinkOrder) { + dashLinkOrder.value = ids.join(","); + } else { + localStorage.setItem('manageFolderOrder', ids.join(",")); } - let draggingElement = null; - let placeholder = null; - let initialX = 0; - let initialY = 0; - let offsetX = 0; - let offsetY = 0; - - // Function to handle the drag start - const handleDragStart = (e, element) => { - draggingElement = element; +} - // Create a placeholder to maintain layout - placeholder = document.createElement('div'); - placeholder.style.height = `${draggingElement.offsetHeight}px`; - placeholder.style.marginBottom = getComputedStyle(draggingElement).marginBottom; - placeholder.classList.add('placeholder'); - draggingElement.parentNode.insertBefore(placeholder, draggingElement); - - const rect = element.getBoundingClientRect(); - initialX = e.clientX; - initialY = e.clientY; - offsetX = e.clientX - rect.left; - offsetY = e.clientY - rect.top; +function loadDashLinkOrder() { + const storedOrder = localStorage.getItem("manageFolderOrder"); // Get the saved order + const bookmarkSort = document.getElementById("bookmarkSort"); - // Prevent the element from moving in the normal flow - element.classList.add('dragging'); - element.style.position = 'absolute'; - draggingElement.style.width = `${rect.width}px`; // Preserve width - draggingElement.style.height = `${rect.height}px`; // Preserve height - element.style.zIndex = 1000; // Bring the element on top of others - element.style.left = `${rect.left}px`; - element.style.top = `${rect.top}px`; - - e.preventDefault(); - }; + if (!storedOrder || !bookmarkSort) return; // Exit if no saved order or no container - // Function to handle dragging movement - const handleDragMove = (e) => { - if (draggingElement) { - const dx = e.clientX - initialX + offsetX; - const dy = e.clientY - initialY + offsetY; - - draggingElement.style.left = `${dx}px`; - draggingElement.style.top = `${dy}px`; - } - }; + const orderArray = storedOrder.split(","); // Convert the saved order into an array + const bookmarkCards = Array.from(document.querySelectorAll("#bookmarkSort .bookmark-card")); - // Function to handle the drag end - const handleDragEnd = () => { - if (draggingElement) { - const rect = draggingElement.getBoundingClientRect(); - - // Reset the position styles of the dragged element - draggingElement.style.position = ''; - draggingElement.style.zIndex = ''; // Reset z-index - draggingElement.style.left = ''; - draggingElement.style.top = ''; - draggingElement.classList.remove('dragging'); - - // Re-insert the element back into the DOM properly - const closestElement = getClosestElement(rect.left, rect.top); - console.error(closestElement.id); - - if (closestElement) { - bookmarkSort.insertBefore(draggingElement, closestElement); - console.log( 'insertBefore' ); - } else { - bookmarkSort.appendChild(draggingElement); - console.log( 'append' ); - } - - // Reorder the elements after the drag ends - reorderElements(); - draggingElement = null; - } - }; - - // Function to reorder the elements inside the container - const reorderElements = () => { - const elements = Array.from(bookmarkSort.children); - const sortedIds = elements.map(element => element.id); - console.log('New order:', sortedIds); - }; - - // Function to handle the drop event - const handleDrop = (e) => { - e.preventDefault(); - }; - - // Helper function to find the closest element based on mouse position - const getClosestElement = (x, y) => { - const elements = Array.from(bookmarkSort.children).filter( - (el) => el !== placeholder && el !== draggingElement - ); - - let closest = null; - let closestDistance = Number.POSITIVE_INFINITY; - - elements.forEach((child) => { - const rect = child.getBoundingClientRect(); - const distance = Math.abs(rect.top - y); - - if (distance < closestDistance) { - closestDistance = distance; - closest = child; - } - }); - - return closest; - }; - - // Attach event listeners to all .card-header elements for dragging - Array.from(document.querySelectorAll('.mover-arrow')).forEach(cardHeader => { - cardHeader.addEventListener('mousedown', (e) => handleDragStart(e, cardHeader.closest('.bookmark-card'))); + // Create a map for quick lookup of cards by their ID + const cardMap = new Map(); + bookmarkCards.forEach(card => { + const match = card.id.match(/folderCard(\d+)/); + if (match) cardMap.set(match[1], card); }); - // Handle the dragging process - document.addEventListener('mousemove', handleDragMove); - document.addEventListener('mouseup', handleDragEnd); - bookmarkSort.addEventListener('dragover', handleDrop); // Listen for the drop event to update the order -}); + // Reorder elements based on the saved order + const orderedElements = []; + orderArray.forEach(id => { + if (cardMap.has(id)) { + orderedElements.push(cardMap.get(id)); // Add elements in the specified order + cardMap.delete(id); // Remove from the map once processed + } + }); + + // Add any remaining (unspecified) elements to the end of the list + const remainingElements = Array.from(cardMap.values()); + orderedElements.push(...remainingElements); + + // Clear the container and append the elements in the new order + bookmarkSort.innerHTML = ""; // Remove all children + orderedElements.forEach(element => bookmarkSort.appendChild(element)); // Append in order +} + \ No newline at end of file diff --git a/app/plugins/bookmarks/models/bookmark_dashboards.php b/app/plugins/bookmarks/models/bookmark_dashboards.php index 1fbcafe..a652af9 100644 --- a/app/plugins/bookmarks/models/bookmark_dashboards.php +++ b/app/plugins/bookmarks/models/bookmark_dashboards.php @@ -41,7 +41,7 @@ class BookmarkDashboards extends DatabaseModel { public function create( $title, $saved_prefs, $link_order, $description = '' ) { if ( ! Check::dataTitle( $title ) ) { - Debug::info( 'Views: illegal title.' ); + Debug::info( 'Dash: illegal title.' ); return false; } $fields = [ @@ -54,8 +54,8 @@ class BookmarkDashboards extends DatabaseModel { 'createdAt' => time(), ]; if ( ! self::$db->insert( $this->tableName, $fields ) ) { - new CustomException( 'viewCreate' ); - Debug::error( "Views: not created " . var_export($fields,true) ); + new CustomException( 'dashCreate' ); + Debug::error( "Dash: not created " . var_export($fields,true) ); return false; } return self::$db->lastId(); @@ -63,11 +63,11 @@ class BookmarkDashboards extends DatabaseModel { public function update( $id, $title, $saved_prefs, $link_order, $description = '' ) { if ( !Check::id( $id ) ) { - Debug::info( 'Views: illegal ID.' ); + Debug::info( 'Dash: illegal ID.' ); return false; } if ( !Check::dataTitle( $title ) ) { - Debug::info( 'Views: illegal title.' ); + Debug::info( 'Dash: illegal title.' ); return false; } $fields = [ @@ -77,8 +77,29 @@ class BookmarkDashboards extends DatabaseModel { 'link_order' => $link_order, ]; if ( !self::$db->update( $this->tableName, $id, $fields ) ) { - new CustomException( 'viewUpdate' ); - Debug::error( "Views: $id not updated" ); + new CustomException( 'dashUpdate' ); + Debug::error( "Dash: $id not updated" ); + return false; + } + return true; + } + + public function updateDash( $id, $saved_prefs = '', $link_order = '' ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Dash: illegal ID.' ); + return false; + } + $fields = []; + $fields['saved_prefs'] = $saved_prefs; + if ( ! empty( $link_order ) ) { + $fields['link_order'] = $link_order; + } + if ( empty( $fields ) ) { + return true; + } + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'dashUpdate' ); + Debug::error( "Dash: $id not updated" ); return false; } return true; diff --git a/app/plugins/bookmarks/models/bookmarks.php b/app/plugins/bookmarks/models/bookmarks.php index 2cdd242..80b0277 100644 --- a/app/plugins/bookmarks/models/bookmarks.php +++ b/app/plugins/bookmarks/models/bookmarks.php @@ -146,16 +146,17 @@ class Bookmarks extends DatabaseModel { return $this->filter( $bookmarks->results() ); } - public function byFolder( $id, $limit = null ) { - $whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND']; - - $whereClause = array_merge( $whereClause, [ 'folderID', '=', $id ] ); - if ( empty( $limit ) ) { - $bookmarks = self::$db->get( $this->tableName, $whereClause ); - } else { - $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + public function byFolder( $id, $hiddenExcluded = false, $archivedExcluded = false ) { + $whereClause = ['createdBy', '=', App::$activeUser->ID ]; + $whereClause = array_merge( $whereClause, [ 'AND', 'folderID', '=', $id ] ); + if ( ! empty( $hiddenExcluded ) ) { + $whereClause = array_merge( $whereClause, [ 'AND', 'hiddenAt', 'is', 'NULL'] ); } - if ( !$bookmarks->count() ) { + if ( ! empty( $archivedExcluded ) ) { + $whereClause = array_merge( $whereClause, [ 'AND', 'archivedAt', 'is', 'NULL'] ); + } + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + if ( ! $bookmarks->count() ) { Debug::info( 'No Bookmarks found.' ); return false; } @@ -343,17 +344,18 @@ class Bookmarks extends DatabaseModel { $instance = $data; $end = true; } - $base_url = $this->getBaseUrl( $instance->url ); - if ( empty( $instance->icon ) ) { - $instance->iconHtml = ''; - } else { - if (strpos($instance->icon, 'http') !== false) { - $instance->iconHtml = ''; - } else { - $instance->iconHtml = ''; + $instance->iconHtml = ''; + if ( ! empty( $instance->icon ) ) { + if ( strpos($instance->icon, 'http') !== false) { + $instance->iconHtml = ''; + } else { + if ( ! empty( $instance->url ) ) { + $base_url = $this->getBaseUrl( $instance->url ); + $instance->iconHtml = ''; + } + } } - } if ( $instance->privacy == 'private' ) { $instance->privacyBadge = 'Private'; @@ -439,7 +441,7 @@ class Bookmarks extends DatabaseModel { return false; } $fields = [ - 'hiddenAt' => 0, + 'hiddenAt' => NULL, ]; if ( !self::$db->update( $this->tableName, $id, $fields ) ) { new CustomException( 'bookmarkUpdate' ); @@ -503,7 +505,7 @@ class Bookmarks extends DatabaseModel { return false; } $fields = [ - 'archivedAt' => 0, + 'archivedAt' => NULL, ]; if ( !self::$db->update( $this->tableName, $id, $fields ) ) { new CustomException( 'bookmarkUpdate' ); @@ -779,7 +781,7 @@ class Bookmarks extends DatabaseModel { } } - function isValidImageUrl($url) { + public function isValidImageUrl($url) { $headers = @get_headers($url); if ($headers && strpos($headers[0], '200') !== false) { diff --git a/app/plugins/bookmarks/plugin.php b/app/plugins/bookmarks/plugin.php index d63831e..ea7d157 100644 --- a/app/plugins/bookmarks/plugin.php +++ b/app/plugins/bookmarks/plugin.php @@ -62,37 +62,37 @@ class Bookmarks extends Plugin { 'default' => 'false', ], 'showArchivedSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing archived bookmarks', + 'pretty' => 'Show archived bookmarks by default', 'type' => 'checkbox', 'default' => 'false', ], 'showHiddenSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing hidden bookmarks', + 'pretty' => 'Show hidden bookmarks by default', 'type' => 'checkbox', 'default' => 'false', ], 'archiveButtonSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing the archive buttons', + 'pretty' => 'Show the archive buttons by default', 'type' => 'checkbox', 'default' => 'false', ], 'visibilityButtonSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing the visibility buttons', + 'pretty' => 'Show the visibility buttons by default', 'type' => 'checkbox', 'default' => 'false', ], 'privacyButtonSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing the privacy buttons', + 'pretty' => 'Show the privacy buttons by default', 'type' => 'checkbox', 'default' => 'true', ], 'shareButtonSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing the share buttons', + 'pretty' => 'Show the share buttons by default', 'type' => 'checkbox', 'default' => 'true', ], 'addButtonSwitch' => [ - 'pretty' => 'Bookmarks default setting for showing the add buttons', + 'pretty' => 'Show the add buttons by default', 'type' => 'checkbox', 'default' => 'true', ], diff --git a/app/plugins/bookmarks/views/bookmarks/create.html b/app/plugins/bookmarks/views/bookmarks/create.html index 29e7f05..bfb7936 100644 --- a/app/plugins/bookmarks/views/bookmarks/create.html +++ b/app/plugins/bookmarks/views/bookmarks/create.html @@ -48,7 +48,7 @@

- +
diff --git a/app/plugins/bookmarks/views/bookmarks/edit.html b/app/plugins/bookmarks/views/bookmarks/edit.html index 4c167e6..fe61a5e 100644 --- a/app/plugins/bookmarks/views/bookmarks/edit.html +++ b/app/plugins/bookmarks/views/bookmarks/edit.html @@ -48,7 +48,7 @@
- +
diff --git a/app/plugins/bookmarks/views/bookmarks/list.html b/app/plugins/bookmarks/views/bookmarks/list.html index 7732d4c..cfbe678 100644 --- a/app/plugins/bookmarks/views/bookmarks/list.html +++ b/app/plugins/bookmarks/views/bookmarks/list.html @@ -19,7 +19,7 @@ {privacy} - + @@ -34,5 +34,5 @@ \ No newline at end of file diff --git a/app/plugins/bookmarks/views/bookmarks/unsorted.html b/app/plugins/bookmarks/views/bookmarks/unsorted.html index d55f2da..5d8c1e7 100644 --- a/app/plugins/bookmarks/views/bookmarks/unsorted.html +++ b/app/plugins/bookmarks/views/bookmarks/unsorted.html @@ -23,7 +23,7 @@ {privacy} - + @@ -38,7 +38,7 @@
- Add + Add
\ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/bookmarkListPanel.html b/app/plugins/bookmarks/views/components/bookmarkListPanel.html index 7340ae5..715727f 100644 --- a/app/plugins/bookmarks/views/components/bookmarkListPanel.html +++ b/app/plugins/bookmarks/views/components/bookmarkListPanel.html @@ -3,9 +3,12 @@
- + + + + {title} - +
@@ -35,8 +38,8 @@ diff --git a/app/plugins/bookmarks/views/components/shareListRows.html b/app/plugins/bookmarks/views/components/shareListRows.html index c79a5b1..06d825d 100644 --- a/app/plugins/bookmarks/views/components/shareListRows.html +++ b/app/plugins/bookmarks/views/components/shareListRows.html @@ -4,7 +4,7 @@ {title}{privacyBadge} {publish} - + -
- - +
\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/edit.html b/app/plugins/bookmarks/views/dashboards/edit.html index d845798..ee6b7c6 100644 --- a/app/plugins/bookmarks/views/dashboards/edit.html +++ b/app/plugins/bookmarks/views/dashboards/edit.html @@ -35,7 +35,7 @@
- +
diff --git a/app/plugins/bookmarks/views/dashboards/folderPanels.html b/app/plugins/bookmarks/views/dashboards/folderPanels.html index 152515a..101df2c 100644 --- a/app/plugins/bookmarks/views/dashboards/folderPanels.html +++ b/app/plugins/bookmarks/views/dashboards/folderPanels.html @@ -3,9 +3,12 @@
- + + + + {title} - +
@@ -35,8 +38,8 @@
diff --git a/app/plugins/bookmarks/views/dashboards/list.html b/app/plugins/bookmarks/views/dashboards/list.html index 95b2a50..fad456b 100644 --- a/app/plugins/bookmarks/views/dashboards/list.html +++ b/app/plugins/bookmarks/views/dashboards/list.html @@ -6,7 +6,7 @@

From here you can easily create, update, or remove any bookmark dashboards you have created.

Dashboards are a feature that allow you to build customized bookmark pages that you can easily save and open la

-
+
@@ -22,7 +22,7 @@ - + @@ -37,7 +37,7 @@
{title} {description}
- Create + Create
diff --git a/app/plugins/bookmarks/views/dashboards/view.html b/app/plugins/bookmarks/views/dashboards/view.html index 2c64b9c..0a3805b 100644 --- a/app/plugins/bookmarks/views/dashboards/view.html +++ b/app/plugins/bookmarks/views/dashboards/view.html @@ -4,7 +4,9 @@
-
+ + +
{folderPanels}

@@ -15,7 +17,7 @@
- +
diff --git a/app/plugins/bookmarks/views/export.html b/app/plugins/bookmarks/views/export.html index 6f8d3f8..ce64985 100644 --- a/app/plugins/bookmarks/views/export.html +++ b/app/plugins/bookmarks/views/export.html @@ -2,9 +2,9 @@
Bookmark Export
-

Select which folders to include in the export

+

Select which folders to include in the export

-
+
@@ -39,9 +39,28 @@
+
+ +
+
+ +
+
Status:
+
+ + +
+
+ + +
+
+
+
+

Literally every browser is chromium based now, so they all have a standard import/export.

- +
diff --git a/app/plugins/bookmarks/views/extensions/brave.html b/app/plugins/bookmarks/views/extensions/brave.html index 8b19a2b..18347c3 100644 --- a/app/plugins/bookmarks/views/extensions/brave.html +++ b/app/plugins/bookmarks/views/extensions/brave.html @@ -55,7 +55,7 @@ diff --git a/app/plugins/bookmarks/views/extensions/chrome.html b/app/plugins/bookmarks/views/extensions/chrome.html index 342b6f4..0290a41 100644 --- a/app/plugins/bookmarks/views/extensions/chrome.html +++ b/app/plugins/bookmarks/views/extensions/chrome.html @@ -55,7 +55,7 @@ diff --git a/app/plugins/bookmarks/views/extensions/edge.html b/app/plugins/bookmarks/views/extensions/edge.html index 68d8b7e..19e2ce5 100644 --- a/app/plugins/bookmarks/views/extensions/edge.html +++ b/app/plugins/bookmarks/views/extensions/edge.html @@ -55,7 +55,7 @@ diff --git a/app/plugins/bookmarks/views/extensions/firefox.html b/app/plugins/bookmarks/views/extensions/firefox.html index 0cdc639..df2e1cb 100644 --- a/app/plugins/bookmarks/views/extensions/firefox.html +++ b/app/plugins/bookmarks/views/extensions/firefox.html @@ -55,7 +55,7 @@ diff --git a/app/plugins/bookmarks/views/extensions/index.html b/app/plugins/bookmarks/views/extensions/index.html index 8fe694e..aba589e 100644 --- a/app/plugins/bookmarks/views/extensions/index.html +++ b/app/plugins/bookmarks/views/extensions/index.html @@ -14,7 +14,7 @@
Chrome Extension

Enhance your Chrome browser with advanced features and effortless organization.

- Learn More + Learn More
@@ -26,7 +26,7 @@
Firefox Extension

Seamlessly integrate with Firefox for a smoother browsing experience.

- Learn More + Learn More @@ -38,7 +38,7 @@
Opera Extension

Boost your Opera browser with intuitive features and tools.

- Learn More + Learn More @@ -50,7 +50,7 @@
Brave Extension

Enjoy secure and private browsing with Brave, enhanced by our extension.

- Learn More + Learn More @@ -62,7 +62,7 @@
Edge Extension

Maximize productivity on Microsoft Edge with our extension.

- Learn More + Learn More diff --git a/app/plugins/bookmarks/views/extensions/opera.html b/app/plugins/bookmarks/views/extensions/opera.html index 35160b2..7ccf3de 100644 --- a/app/plugins/bookmarks/views/extensions/opera.html +++ b/app/plugins/bookmarks/views/extensions/opera.html @@ -55,7 +55,7 @@ diff --git a/app/plugins/bookmarks/views/folders/create.html b/app/plugins/bookmarks/views/folders/create.html index f376103..44b6426 100644 --- a/app/plugins/bookmarks/views/folders/create.html +++ b/app/plugins/bookmarks/views/folders/create.html @@ -40,7 +40,7 @@
- +
diff --git a/app/plugins/bookmarks/views/folders/edit.html b/app/plugins/bookmarks/views/folders/edit.html index 89c04fc..5eb4e4c 100644 --- a/app/plugins/bookmarks/views/folders/edit.html +++ b/app/plugins/bookmarks/views/folders/edit.html @@ -38,7 +38,7 @@
- +
diff --git a/app/plugins/bookmarks/views/folders/list.html b/app/plugins/bookmarks/views/folders/list.html index e9cbe11..019c76e 100644 --- a/app/plugins/bookmarks/views/folders/list.html +++ b/app/plugins/bookmarks/views/folders/list.html @@ -14,7 +14,7 @@ {prettyPrivacy} {description} - + @@ -30,5 +30,5 @@
- Create + Create
\ No newline at end of file diff --git a/app/plugins/bookmarks/views/folders/view.html b/app/plugins/bookmarks/views/folders/view.html index 2ac4fc8..22124b9 100644 --- a/app/plugins/bookmarks/views/folders/view.html +++ b/app/plugins/bookmarks/views/folders/view.html @@ -45,7 +45,7 @@
- +
diff --git a/app/plugins/bookmarks/views/nav/viewOptions.html b/app/plugins/bookmarks/views/nav/viewOptions.html index 5435562..de5e889 100644 --- a/app/plugins/bookmarks/views/nav/viewOptions.html +++ b/app/plugins/bookmarks/views/nav/viewOptions.html @@ -58,7 +58,7 @@ diff --git a/app/plugins/members/controllers/admin/members.php b/app/plugins/members/controllers/admin/members.php index 53dd9ef..944acdf 100644 --- a/app/plugins/members/controllers/admin/members.php +++ b/app/plugins/members/controllers/admin/members.php @@ -19,6 +19,8 @@ use TheTempusProject\Classes\AdminController; use TheTempusProject\Plugins\Members as MemberModel; use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Hermes\Functions\Route as Routes; +use TheTempusProject\Hermes\Functions\Redirect; class Members extends AdminController { public function __construct() { @@ -28,13 +30,46 @@ class Members extends AdminController { } public function index( $data = null ) { + self::$title = 'Admin - Membership Scripts'; + Views::view( 'members.admin.scripts' ); + } + + public function orphans( $data = null, $id = null ) { + self::$title = 'Admin - Orphaned PRoducts'; + if ( $data = 'abandon' && ! empty( $id ) ) { + MemberModel::orphanAbandon( $id ); + Redirect::to('admin/members/orphans'); + } + $orphans = MemberModel::findOrphans(); + Views::view( 'members.admin.orphans', $orphans ); + } + + public function webhooks( $data = null, $id = null ) { self::$title = 'Admin - Membership Webhooks'; + + if ( $data = 'delete' && ! empty( $id ) ) { + MemberModel::webhookRemove( $id ); + Redirect::to('admin/members/webhooks'); + } + $data = []; + $webhooks = MemberModel::webhookList(); + foreach ($webhooks->data as $key => $webhook) { + $hook = new \stdClass; + $hook->id = $webhook->id; + $hook->enabled_events = implode( ',
', $webhook->enabled_events ); + $hook->status = $webhook->status; + $hook->url = $webhook->url; + $data[] = $hook; + } + Components::set( 'urltouse', Routes::getAddress() ); + if ( !Input::exists( 'submit' ) ) { - return Views::view( 'members.admin.webhooks' ); + return Views::view( 'members.admin.webhooks', $data ); } MemberModel::webhookSetup(); Issues::add( 'success', 'Webhooks Generated' ); Issues::add( 'error', 'Now, LEAVE!' ); + Redirect::to('admin/members/webhooks'); } } diff --git a/app/plugins/members/controllers/member.php b/app/plugins/members/controllers/member.php index f89858a..51b2e22 100644 --- a/app/plugins/members/controllers/member.php +++ b/app/plugins/members/controllers/member.php @@ -70,7 +70,7 @@ class Member extends Controller { } try { $session = self::$stripe->billingPortal->sessions->create([ - 'customer' => $customer, + 'customer' => $customer->stripe_customer, 'return_url' => Routes::getAddress() . 'member/manage', ]); } catch (\Stripe\Exception\InvalidRequestException $e) { @@ -174,7 +174,7 @@ class Member extends Controller { Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' ); return Redirect::home(); } - Views::view( 'members.landing1', $product ); + Views::view( 'members.landing', $product ); } public function checkout( $plan = 'monthly' ) { @@ -187,6 +187,19 @@ class Member extends Controller { Issues::add( 'error', 'no customer' ); return $this->index(); } + + $plan = strtolower( $plan ); + if ( ! in_array( $plan, ['monthly','yearly','upgrade'] ) ) { + Session::flash( 'error', 'Unknown plan' ); + return Redirect::to( 'home/index' ); + } + if ( $plan === 'upgrade' ) { + $plan = 'yearly'; + $successUrl = Routes::getAddress() . 'member/payment/upgrade?session_id={CHECKOUT_SESSION_ID}'; + } else { + $successUrl = Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}'; + } + $stripePrice = $this->findPrice( $plan ); $session = self::$stripe->checkout->sessions->create([ @@ -197,7 +210,7 @@ class Member extends Controller { 'quantity' => 1, ]], 'mode' => 'subscription', - 'success_url' => Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}', + 'success_url' => $successUrl, 'cancel_url' => Routes::getAddress() . 'member/payment/cancel', ]); header('Location: ' . $session->url); @@ -206,7 +219,7 @@ class Member extends Controller { public function payment( $type = '' ) { $type = strtolower( $type ); - if ( ! in_array( $type, ['cancel','complete'] ) ) { + if ( ! in_array( $type, ['cancel','complete','upgrade'] ) ) { Session::flash( 'error', 'Unknown Payment' ); return Redirect::to( 'home/index' ); } @@ -216,6 +229,11 @@ class Member extends Controller { return Views::view( 'members.paymentcanceled' ); } + if ( $type == 'upgrade' ) { + self::$title = 'Better Members Area'; + return Views::view( 'members.upgradeCompleted' ); + } + self::$title = '(almost) Members Area'; Views::view( 'members.paymentcomplete' ); } diff --git a/app/plugins/members/models/membership_products.php b/app/plugins/members/models/membership_products.php index 076467c..a4ab727 100644 --- a/app/plugins/members/models/membership_products.php +++ b/app/plugins/members/models/membership_products.php @@ -27,8 +27,8 @@ class MembershipProducts extends DatabaseModel { public $databaseMatrix = [ [ 'name', 'varchar', '128' ], [ 'description', 'text', '' ], - [ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922 - [ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922 + [ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4,399.22 === 439922 + [ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4,399.22 === 439922 [ 'stripe_product', 'varchar', '64' ], // not-required to create - generated by stripe after [ 'stripe_price_monthly', 'varchar', '64' ], // not-required to create - generated by stripe after [ 'stripe_price_yearly', 'varchar', '64' ], // not-required to create - generated by stripe after @@ -166,7 +166,7 @@ class MembershipProducts extends DatabaseModel { public function updateStripeYearlyPrice( $product, $yearly_price ) { $stripe->prices->update( - $yearly->id, + $yearly_price, ['lookup_key' => ""] ); return $this->createStripeYearlyPrice( $product, $yearly_price ); @@ -174,7 +174,7 @@ class MembershipProducts extends DatabaseModel { public function updateStripeMonthlyPrice( $product, $monthly_price ) { $stripe->prices->update( - $monthly->id, + $monthly_price, ['lookup_key' => ""] ); return $this->createStripeMonthlyPrice( $product, $monthly_price ); @@ -208,7 +208,7 @@ class MembershipProducts extends DatabaseModel { if ( ! $data->count() ) { return false; } - return $data->first(); + return $this->filter( $data->first() ); } public function mainProduct() { diff --git a/app/plugins/members/plugin.php b/app/plugins/members/plugin.php index 3e0a658..54b0adb 100644 --- a/app/plugins/members/plugin.php +++ b/app/plugins/members/plugin.php @@ -19,6 +19,7 @@ use TheTempusProject\Bedrock\Classes\Config; use TheTempusProject\Hermes\Functions\Route as Routes; use TheTempusProject\Models\Memberships; use TheTempusProject\Models\MembershipProducts as Products; +use TheTempusProject\Canary\Bin\Canary as Debug; class Members extends Plugin { public static $stripe; @@ -46,17 +47,17 @@ class Members extends Plugin { 'text' => ' Memberships', 'url' => [ [ - 'text' => ' Products', + 'text' => ' Products', 'url' => '{ROOT_URL}admin/products', ], [ - 'text' => ' Subscriptions', + 'text' => ' Subscriptions', 'url' => '{ROOT_URL}admin/records', ], - // [ - // 'text' => ' Invoices', - // 'url' => '{ROOT_URL}admin/invoices', - // ], + [ + 'text' => ' Scripts', + 'url' => '{ROOT_URL}admin/members', + ], ], ], ]; @@ -116,41 +117,52 @@ class Members extends Plugin { ]; public function __construct( $load = false ) { - if ( ! self::$loaded ) { - App::$userCPlinks[] = (object) self::$userLinks; - self::$loaded = true; + if ( ! self::$loaded && $load ) { + if ( App::$isLoggedIn ) { + App::$isMember = $this->groupHasMemberAccess( App::$activeGroup ); + if ( empty( App::$isMember ) ) { + App::$isMember = $this->userHasActiveMembership( App::$activeUser->ID ); + } + } + $this->filters[] = [ + 'name' => 'member', + 'find' => '#{MEMBER}(.*?){/MEMBER}#is', + 'replace' => ( App::$isMember ? '$1' : '' ), + 'enabled' => true, + ]; + $this->filters[] = [ + 'name' => 'nonmember', + 'find' => '#{NONMEMBER}(.*?){/NONMEMBER}#is', + 'replace' => ( App::$isLoggedIn && App::$isMember == false ? '$1' : '' ), + 'enabled' => true, + ]; + $this->filters[] = [ + 'name' => 'upgrade', + 'find' => '#{UPGRADE}(.*?){/UPGRADE}#is', + 'replace' => ( App::$isLoggedIn && ( App::$isMember === 'monthly' ) ? '$1' : '' ), + 'enabled' => true, + ]; } - if ( App::$isLoggedIn ) { - App::$isMember = $this->groupHasMemberAccess( App::$activeGroup ); - if ( empty( App::$isMember ) ) { - App::$isMember = $this->userHasActiveMembership( App::$activeUser->ID ); + + parent::__construct( $load ); + + if ( $this->checkEnabled() && App::$isLoggedIn ) { + if ( ! self::$loaded && $load ) { + App::$userCPlinks[] = (object) self::$userLinks; + App::$topNavRightDropdown .= '
  • Subscriptions
  • '; } } - $this->filters[] = [ - 'name' => 'member', - 'find' => '#{MEMBER}(.*?){/MEMBER}#is', - 'replace' => ( App::$isMember ? '$1' : '' ), - 'enabled' => true, - ]; - $this->filters[] = [ - 'name' => 'nonmember', - 'find' => '#{NONMEMBER}(.*?){/NONMEMBER}#is', - 'replace' => ( App::$isLoggedIn && App::$isMember == false ? '$1' : '' ), - 'enabled' => true, - ]; - $this->filters[] = [ - 'name' => 'upgrade', - 'find' => '#{UPGRADE}(.*?){/UPGRADE}#is', - 'replace' => ( App::$isLoggedIn && ( App::$isMember === 'monthly' ) ? '$1' : '' ), - 'enabled' => true, - ]; + + if ( ! self::$loaded && $load ) { + self::$loaded = true; + } + $api_key = Config::getValue( 'memberships/stripeSecret' ); if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) { self::$stripe = false; } else { self::$stripe = new StripeClient( $api_key ); } - parent::__construct( $load ); } public function groupHasMemberAccess( $activeGroup ) { @@ -169,6 +181,10 @@ class Members extends Plugin { $products = new Products; $product = $products->findByPriceID( $membership->subscription_price_id ); + if ( empty( $product ) ) { + Debug::error('Active membership on non-existent product'); + return false; + } if ( $product->stripe_price_monthly == $membership->subscription_price_id ) { return 'monthly'; } @@ -177,7 +193,6 @@ class Members extends Plugin { public static function webhookSetup() { $root = Routes::getAddress(); - // $root = "https://stripe.joeykimsey.com/"; $response = self::$stripe->webhookEndpoints->create([ 'enabled_events' => self::$webhookEvents, 'url' => $root . 'api/stripe/webhook', @@ -185,13 +200,66 @@ class Members extends Plugin { return $response; } - // public static function webhookSetup() { - // $root = Routes::getAddress(); - // $root = "https://stripe.joeykimsey.com/"; - // $response = self::$stripe->webhookEndpoints->create([ - // 'enabled_events' => self::$webhookEvents, - // 'url' => $root . 'api/stripe/webhook', - // ]); - // return $response; - // } + public static function webhookList() { + $response = self::$stripe->webhookEndpoints->all(['limit' => 25]); + return $response; + } + + public static function webhookRemove( $id ) { + $response = self::$stripe->webhookEndpoints->delete( $id, []); + return $response; + } + + public static function orphanAbandon( $id ) { + $response = self::$stripe->prices->update( + $id, + ['lookup_key' => ""] + ); + return $response; + } + + public static function findOrphans() { + $orphans = []; + // any prices with keys not represented in our local db will show as an orphan + + $result = self::$stripe->prices->search([ + 'query' => 'lookup_key:"membership-monthly"', + ]); + + $products = new Products; + if ( ! empty( $result->data ) ) { + $product = $products->findByPriceID( $result->data[0]->id ); + + if ( empty( $product ) ) { + $data = $result->data[0]; + + $child = new \stdClass; + $child->price_id = $data->id; + $child->product = $data->product; + $child->lookup_key = $data->lookup_key; + $child->amount = $data->unit_amount; + $orphans[] = $child; + } + } + + $result = self::$stripe->prices->search([ + 'query' => 'lookup_key:"membership-yearly"', + ]); + if ( ! empty( $result->data ) ) { + $product = $products->findByPriceID( $result->data[0]->id ); + + if ( empty( $product ) ) { + $data = $result->data[0]; + + $child = new \stdClass; + $child->price_id = $data->id; + $child->product = $data->product; + $child->lookup_key = $data->lookup_key; + $child->amount = $data->unit_amount; + $orphans[] = $child; + } + } + + return $orphans; + } } diff --git a/app/plugins/members/views/admin/orphans.html b/app/plugins/members/views/admin/orphans.html new file mode 100644 index 0000000..e3246af --- /dev/null +++ b/app/plugins/members/views/admin/orphans.html @@ -0,0 +1,35 @@ +
    + Stripe Webhook Generation +
    + {ADMIN_BREADCRUMBS} +

    Orphans are stripe prices that have unique lookup_keys used by the membership system, but have no products currently saved.

    + + + + + + + + + + + + {LOOP} + + + + + + + + {/LOOP} + {ALT} + + + + {/ALT} + +
    price idamountlookup keyurl
    {price_id}{amount}{lookup_key}
    + No results to show. +
    +
    \ No newline at end of file diff --git a/app/plugins/members/views/admin/products/create.html b/app/plugins/members/views/admin/products/create.html index beed6d6..b26ec45 100644 --- a/app/plugins/members/views/admin/products/create.html +++ b/app/plugins/members/views/admin/products/create.html @@ -45,7 +45,7 @@
    - +
    \ No newline at end of file diff --git a/app/plugins/members/views/admin/scripts.html b/app/plugins/members/views/admin/scripts.html new file mode 100644 index 0000000..b170924 --- /dev/null +++ b/app/plugins/members/views/admin/scripts.html @@ -0,0 +1,13 @@ +
    + Membership Scripts +
    + {ADMIN_BREADCRUMBS} + +
    \ No newline at end of file diff --git a/app/plugins/members/views/admin/webhooks.html b/app/plugins/members/views/admin/webhooks.html index d895635..0a8b5c1 100644 --- a/app/plugins/members/views/admin/webhooks.html +++ b/app/plugins/members/views/admin/webhooks.html @@ -1,10 +1,43 @@ -WARNING: Regenerating existing webhooks makes joey sad, don't do iit! -
    -
    - -
    - - -
    +
    + Stripe Webhook Generation +
    + {ADMIN_BREADCRUMBS} + + + + + + + + + + + + {LOOP} + + + + + + + + {/LOOP} + {ALT} + + + + {/ALT} + +
    idstatusenabled_eventsurl
    {id}{status}{enabled_events}{url}
    + No results to show. +
    +
    +
    WARNING: Regenerating existing webhooks makes joey sad, don't do iit!
    +

    The new webhooks will be generated using the base url: {urltouse}

    + +
    + +
    +
    - \ No newline at end of file +
    \ No newline at end of file diff --git a/app/plugins/members/views/cancel.html b/app/plugins/members/views/cancel.html index cddbe15..af476c3 100644 --- a/app/plugins/members/views/cancel.html +++ b/app/plugins/members/views/cancel.html @@ -1,21 +1,20 @@ -
    -
    -
    -

    Are You Sure You Want to Cancel?

    +
    +

    Are You Sure You Want to Cancel?

    +
    +

    - Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits. + Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits.

    - Consider staying to continue enjoying the full experience of our service. + Consider staying to continue enjoying the full experience of our service.

    -
    -
    \ No newline at end of file +
    \ No newline at end of file diff --git a/app/plugins/members/views/landing1.html b/app/plugins/members/views/landing.html similarity index 88% rename from app/plugins/members/views/landing1.html rename to app/plugins/members/views/landing.html index cdda656..d9c397a 100644 --- a/app/plugins/members/views/landing1.html +++ b/app/plugins/members/views/landing.html @@ -1,4 +1,3 @@ -

    Compare plans

    @@ -31,7 +30,7 @@ - Share bookmarks abd folders + Share bookmarks and folders @@ -88,9 +87,6 @@
  • Access from any device
  • Share access with anyone
  • - - Sign-Up for Free -
    @@ -100,7 +96,7 @@

    Monthly

    -

    $4.99/month

    +

    {prettyPriceMonthly}/month

    - + Get started
    @@ -120,11 +116,11 @@

    Yearly

    -

    $19.99/year

    +

    {prettyPriceYearly}/year

    - + Get started
    diff --git a/app/plugins/members/views/manage.html b/app/plugins/members/views/manage.html index a45cf86..bee1148 100644 --- a/app/plugins/members/views/manage.html +++ b/app/plugins/members/views/manage.html @@ -1,7 +1,8 @@ -
    +
    +

    Manage Memberships

    +
    - Memberships @@ -23,13 +24,13 @@ @@ -43,7 +44,7 @@ {/ALT}
    {DTC=date}{current_period_start}{/DTC} {DTC=date}{current_period_end}{/DTC} - + - - + +
    - + Manage Payment Method
    diff --git a/app/plugins/members/views/members.html b/app/plugins/members/views/members.html index c4d262a..96636da 100644 --- a/app/plugins/members/views/members.html +++ b/app/plugins/members/views/members.html @@ -11,7 +11,8 @@ Right now, this entire system was built and managed by myself. I have used my own version of this for years, but translating it to a publicly available product is not a 1-to-1 job. There may be bugs or issues encountered while you use the product. I can't guarantee a fix for every need in every case immediately, but I do actively keep track of bugs and work hard to ensure everyone has a great experience using the app.

    - {loggedin}Report a Bug{/loggedin} + {loggedin}Report a Bug{/loggedin} + Manage Membership Contact Us
    \ No newline at end of file diff --git a/app/plugins/members/views/pause.html b/app/plugins/members/views/pause.html index db3f84a..bd09161 100644 --- a/app/plugins/members/views/pause.html +++ b/app/plugins/members/views/pause.html @@ -1,21 +1,20 @@ -
    -
    -
    -

    Are You Sure You Want to Pause?

    +
    +

    Are You Sure You Want to Pause?

    +
    +

    - Pausing your subscription means you'll miss out on exclusive features, updates, and benefits. + Pausing your subscription means you'll miss out on exclusive features, updates, and benefits.

    - Consider staying to continue enjoying the full experience of our service. + Consider staying to continue enjoying the full experience of our service.

    -
    -
    \ No newline at end of file +
    \ No newline at end of file diff --git a/app/plugins/members/views/paymentcanceled.html b/app/plugins/members/views/paymentcanceled.html index ec98c3c..2dc6a47 100644 --- a/app/plugins/members/views/paymentcanceled.html +++ b/app/plugins/members/views/paymentcanceled.html @@ -1,4 +1,14 @@ -
    -

    Take your time, its not a sprint, its a marathon.

    -

    Its ok, no-one wants to checkout too fast, its embarrassing.

    +
    +

    Almost there!

    +

    + Take your time, its not a sprint, its a marathon. + Nno-one wants to checkout too fast, its embarrassing. +

    +

    + If you think this is a tool that you could really use and can't afford it, use the contact form below and tell me why you need it. +

    +
    + {loggedin}Report a Bug{/loggedin} + Contact Us +
    \ No newline at end of file diff --git a/app/plugins/members/views/paymentcomplete.html b/app/plugins/members/views/paymentcomplete.html index 962ac77..1af195d 100644 --- a/app/plugins/members/views/paymentcomplete.html +++ b/app/plugins/members/views/paymentcomplete.html @@ -1,5 +1,14 @@ -
    -

    Thanks for joining!

    -

    Its people like you who keep the pixels on around here. - Its people like me who tell you life is unfair and it could take up to an hour for your membership to activate.

    +
    +

    Thanks for joining!

    +

    + Its people like you who keep the pixels on around here. + Its people like me who tell people like you that unfortunately, it could take up to an hour for your membership to activate. +

    +

    + With that said, its usually instant, and if its taking too long, just reach out to us via the contact form. +

    +
    + {loggedin}Report a Bug{/loggedin} + Contact Us +
    \ No newline at end of file diff --git a/app/plugins/members/views/upgrade.html b/app/plugins/members/views/upgrade.html index 176c39d..8e5d4a7 100644 --- a/app/plugins/members/views/upgrade.html +++ b/app/plugins/members/views/upgrade.html @@ -1,6 +1,6 @@
    -

    Upgrade to a Yearly Plan

    +

    Upgrade to a Yearly Plan

    Save more and enjoy uninterrupted access to all features with our yearly plan!

    @@ -10,12 +10,12 @@

    diff --git a/app/plugins/members/views/upgradeCompleted.html b/app/plugins/members/views/upgradeCompleted.html new file mode 100644 index 0000000..7bc7811 --- /dev/null +++ b/app/plugins/members/views/upgradeCompleted.html @@ -0,0 +1,13 @@ +
    +

    Thanks for the vote of confidence!

    +

    + While you were more profitable as a monthly user, I do appreciate the vote of confidence it takes to commit to 4x the cost at once. +

    +

    + There really is no more benefit than you saving money, so enjoy the features you have already come to know and enjoy. +

    +
    + {loggedin}Report a Bug{/loggedin} + Contact Us +
    +
    \ No newline at end of file diff --git a/app/views/index.html b/app/views/index.html index cc73ddc..27a2895 100644 --- a/app/views/index.html +++ b/app/views/index.html @@ -4,7 +4,7 @@
    diff --git a/app/views/user_cp/password_change.html b/app/views/user_cp/password_change.html index e808d69..02ad5c7 100644 --- a/app/views/user_cp/password_change.html +++ b/app/views/user_cp/password_change.html @@ -19,7 +19,7 @@
    - +
    diff --git a/app/views/user_cp/settings.html b/app/views/user_cp/settings.html index 565eb61..2886fd4 100644 --- a/app/views/user_cp/settings.html +++ b/app/views/user_cp/settings.html @@ -2,13 +2,13 @@

    Preferences


    -
    +
    {PREFERENCES_FORM}
    -
    +