{title}
- +{title}
- + {content} {ADMIN}
diff --git a/app/plugins/blog/views/widgets/archive.html b/app/plugins/blog/views/widgets/archive.html index 63fb52d..40b42c1 100644 --- a/app/plugins/blog/views/widgets/archive.html +++ b/app/plugins/blog/views/widgets/archive.html @@ -6,7 +6,7 @@
-
{LOOP}
-
- ({count}) {monthText} {year} +
- ({count}) {monthText} {year} {/LOOP} {ALT}
- None To Show diff --git a/app/plugins/bookmarks/controllers/api/bookmark_folders.php b/app/plugins/bookmarks/controllers/api/bookmark_folders.php new file mode 100644 index 0000000..b76749a --- /dev/null +++ b/app/plugins/bookmarks/controllers/api/bookmark_folders.php @@ -0,0 +1,68 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Api; + +use TheTempusProject\Models\User; +use TheTempusProject\Classes\ApiController; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Models\Folders; + +class BookmarkFolders extends ApiController { + protected static $folders; + + public function __construct() { + header('Access-Control-Allow-Origin: *'); + parent::__construct(); + self::$folders = new Folders; + } + + public function create() { + $user = self::$authToken->createdBy; + + $payload = @file_get_contents('php://input'); + $payload = json_decode( $payload, true ); + + $result = self::$folders->create( + $payload['name'], + $payload['folder'] ?? 0, + $payload['notes'] ?? '', + $payload['color'] ?? 'default', + $payload['privacy'] ?? 'private', + $user + ); + + if ( ! $result ) { + $responseType = 'error'; + $response = 'There was an error creating your folder.'; + } else { + $responseType = 'id'; + $response = $result; + } + Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } + + public function list( $id = '' ) { + $user = self::$authToken->createdBy; + $folders = self::$folders->bySpecificUser( $user ); + + if ( ! $folders ) { + $responseType = 'error'; + $response = 'There was an error creating your folder.'; + } else { + $responseType = 'folders'; + $response = $folders; + } + Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } +} diff --git a/app/plugins/bookmarks/controllers/api/bookmarks.php b/app/plugins/bookmarks/controllers/api/bookmarks.php new file mode 100644 index 0000000..1903f78 --- /dev/null +++ b/app/plugins/bookmarks/controllers/api/bookmarks.php @@ -0,0 +1,56 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Api; + +use TheTempusProject\Models\User; +use TheTempusProject\Classes\ApiController; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Models\Bookmarks as Bookmark; + +class Bookmarks extends ApiController { + protected static $bookmarks; + + public function __construct() { + parent::__construct(); + self::$bookmarks = new Bookmark; + } + + public function create() { + header('Access-Control-Allow-Origin: *'); + + $user = self::$authToken->createdBy; + + $payload = @file_get_contents('php://input'); + $payload = json_decode( $payload, true ); + + $result = self::$bookmarks->create( + $payload['name'], + $payload['url'], + $payload['folder'] ?? 0, + $payload['notes'] ?? '', + $payload['color'] ?? 'default', + $payload['privacy'] ?? 'private', + 'external', + $user + ); + if ( ! $result ) { + $responseType = 'error'; + $response = 'There was an error creating your folder.'; + } else { + $responseType = 'data'; + $response = $result; + } + Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } +} \ No newline at end of file diff --git a/app/plugins/bookmarks/controllers/bookmarks.php b/app/plugins/bookmarks/controllers/bookmarks.php new file mode 100644 index 0000000..434bb37 --- /dev/null +++ b/app/plugins/bookmarks/controllers/bookmarks.php @@ -0,0 +1,992 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers; + +use TheTempusProject\Hermes\Functions\Redirect; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Bedrock\Functions\Session; +use TheTempusProject\Houdini\Classes\Issues; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Classes\Controller; +use TheTempusProject\Classes\Forms; +use TheTempusProject\Models\Bookmarks as Bookmark; +use TheTempusProject\Models\Folders; +use TheTempusProject\Models\BookmarkDashboards as Dashboards; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Houdini\Classes\Forms as HoudiniForms; +use TheTempusProject\Houdini\Classes\Navigation; +use TheTempusProject\Houdini\Classes\Template; +use TheTempusProject\Hermes\Functions\Route as Routes; +use TheTempusProject\Models\User; +use TheTempusProject\Classes\Preferences; +use TheTempusProject\Canary\Bin\Canary as Debug; + +class Bookmarks extends Controller { + protected static $bookmarks; + protected static $folders; + protected static $dashboards; + + public function __construct() { + parent::__construct(); + if ( ! App::$isLoggedIn ) { + Session::flash( 'notice', 'You must be logged in to create or manage bookmarks.' ); + return Redirect::home(); + } + self::$bookmarks = new Bookmark; + self::$folders = new Folders; + self::$dashboards = new Dashboards; + self::$title = 'Bookmarks - {SITENAME}'; + self::$pageDescription = 'Add and save url bookmarks here.'; + + $folderTabs = Views::simpleView( 'bookmarks.nav.folderTabs' ); + if ( stripos( Input::get('url'), 'bookmarks/bookmarks' ) !== false ) { + $tabsView = Navigation::activePageSelect( $folderTabs, '/bookmarks/folders/', false, true ); + $userFolderTabs = Views::simpleView('bookmarks.nav.userFolderTabs', self::$folders->simpleObjectByUser(true) ); + $userFolderTabsView = Navigation::activePageSelect( $userFolderTabs, Input::get( 'url' ), false, true ); + } else { + $tabsView = Navigation::activePageSelect( $folderTabs, Input::get( 'url' ), false, true ); + $userFolderTabsView = ''; + } + Components::set( 'userFolderTabs', $userFolderTabsView ); + Components::set( 'SITE_URL', Routes::getAddress() ); + Views::raw( $tabsView ); + Components::append( 'TEMPLATE_JS_INCLUDES', Template::parse('' ) ); + $viewOptions = Views::simpleView( 'bookmarks.nav.viewOptions' ); + Components::set( 'VIEW_OPTIONS', $viewOptions ); + $dashOptions = Views::simpleView( 'bookmarks.dashboards.dashOptions' ); + Components::set( 'DASH_OPTIONS', $dashOptions ); + $this->setPrefToggles(); + } + + public function index() { + self::$title = 'Manage Bookmarks - {SITENAME}'; + if ( Input::exists('submit') ) { + $prefs = new Preferences; + $user = new User; + $fields = $prefs->convertFormToArray( true ); + $out = $user->updatePrefs( $fields, App::$activeUser->ID ); + $this->setPrefToggles(); + } + + $folders = self::$folders->byUser(); + $panelArray = []; + if ( !empty( $folders ) ) { + foreach ( $folders as $folder ) { + $panel = new \stdClass(); + $folderObject = new \stdClass(); + $folderObject->bookmarks = self::$bookmarks->byFolder( $folder->ID ); + $folderObject->ID = $folder->ID; + $folderObject->title = $folder->title; + $folderObject->color = $folder->color; + $folderObject->uuid = $folder->uuid; + $folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.bookmarkListRows', $folderObject->bookmarks ); + $panelArray[] = $folderObject; + } + } + if ( ! empty( $folders ) ) { + Components::set( 'folderPanels', Views::simpleView( 'bookmarks.components.bookmarkListPanel', $panelArray ) ); + return Views::view( 'bookmarks.dash' ); + } + return Views::view( 'bookmarks.indexExplainer' ); + } + + /** + * Bookmarks + */ + public function bookmark( $id = 0 ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + Navigation::setCrumbComponent( 'BookmarkBreadCrumbs', 'bookmarks/bookmark/' . $id ); + return Views::view( 'bookmarks.bookmarks.view', $bookmark ); + } + + public function unsorted() { + self::$title = 'Unsorted Bookmarks - {SITENAME}'; + $bookmarks = self::$bookmarks->noFolder(); + Views::view( 'bookmarks.bookmarks.unsorted', $bookmarks ); + } + + public function bookmarks( $id = null ) { + $folder = self::$folders->findById( $id ); + if ( $folder == false ) { + Session::flash( 'error', 'Folder not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $folder->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to view this folder.' ); + return Redirect::to( 'bookmarks/index' ); + } + Navigation::setCrumbComponent( 'BookmarkBreadCrumbs', 'bookmarks/bookmarks/' . $id ); + + $panelArray = []; + $panel = new \stdClass(); + $folderObject = new \stdClass(); + $folderObject->bookmarks = self::$bookmarks->byFolder( $folder->ID ); + $folderObject->ID = $folder->ID; + $folderObject->title = $folder->title; + $folderObject->color = $folder->color; + $folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.bookmarkListRows', $folderObject->bookmarks ); + $panel->panel = Views::simpleView( 'bookmarks.components.bookmarkListPanel', [$folderObject] ); + $panelArray[] = $panel; + + return Views::view( 'bookmarks.bookmarks.listPage', $panelArray ); + } + + 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 ); + + if ( ! Input::exists() ) { + return Views::view( 'bookmarks.bookmarks.create' ); + } + if ( ! Forms::check( 'createBookmark' ) ) { + Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.bookmarks.create' ); + } + + $result = self::$bookmarks->create( + Input::post('title'), + Input::post('url'), + $folderID, + Input::post('description'), + Input::post('color'), + Input::post('privacy'), + ); + + if ( ! $result ) { + Issues::add( 'error', [ 'There was an error creating your bookmark.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.bookmarks.create' ); + } + // self::$bookmarks->refreshInfo( $result ); + Session::flash( 'success', 'Your Bookmark has been created.' ); + if ( ! empty( $folderID ) ) { + Redirect::to( 'bookmarks/bookmarks/'. $folderID ); + } else { + Redirect::to( 'bookmarks/index' ); + } + } + + public function editBookmark( $id = null ) { + self::$title = 'Edit Bookmark - {SITENAME}'; + $folderID = Input::exists('folder_id') ? Input::post('folder_id') : ''; + + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Issues::add( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Issues::add( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( empty( $folderID ) ) { + $folderID = $bookmark->folderID; + } + + $this->setFolderSelect( $folderID ); + Components::set( 'color', $bookmark->color ); + + if ( ! Input::exists( 'submit' ) ) { + return Views::view( 'bookmarks.bookmarks.edit', $bookmark ); + } + if ( ! Forms::check( 'editBookmark' ) ) { + Issues::add( 'error', [ 'There was an error updating your bookmark.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.bookmarks.edit', $bookmark ); + } + + $result = self::$bookmarks->update( + $id, + Input::post('title'), + Input::post('url'), + $folderID, + Input::post('description'), + Input::post('color'), + Input::post('privacy'), + ); + if ( ! $result ) { + Issues::add( 'error', [ 'There was an error updating your bookmark.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.bookmarks.edit', $bookmark ); + } + Session::flash( 'success', 'Your Bookmark has been updated.' ); + Redirect::to( 'bookmarks/folders/'. $bookmark->folderID ); + } + + public function deleteBookmark( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Issues::add( 'error', 'Bookmark not found.' ); + return $this->index(); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Issues::add( 'error', 'You do not have permission to modify this bookmark.' ); + return $this->index(); + } + $result = self::$bookmarks->delete( $id ); + if ( !$result ) { + Session::flash( 'error', 'There was an error deleting the bookmark(s)' ); + } else { + Session::flash( 'success', 'Bookmark deleted' ); + } + Redirect::to( 'bookmarks/folders/'. $bookmark->folderID ); + } + + /** + * Folders + */ + public function folders( $id = null) { + $folder = self::$folders->findById( $id ); + if ( $folder == false ) { + $folders = self::$folders->byUser(); + Components::set( 'foldersList', Views::simpleView( 'bookmarks.folders.list', $folders ) ); + return Views::view( 'bookmarks.folders.listPage' ); + } + if ( $folder->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to view this folder.' ); + return Redirect::to( 'bookmarks/index' ); + } + Navigation::setCrumbComponent( 'BookmarkBreadCrumbs', 'bookmarks/folders/' . $id ); + return Views::view( 'bookmarks.folders.view', $folder ); + } + + 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 ) ) { + $this->setFolderSelect( $folderID ); + } else { + $folderSelect = ''; + Components::set( 'folderSelect', $folderSelect ); + } + if ( ! Input::exists() ) { + return Views::view( 'bookmarks.folders.create' ); + } + if ( ! Forms::check( 'createFolder' ) ) { + Issues::add( 'error', [ 'There was an error creating your folder.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.folders.create' ); + } + $folder = self::$folders->create( Input::post('title'), $folderID, Input::post('description'), Input::post('color'), Input::post('privacy') ); + if ( ! $folder ) { + return Views::view( 'bookmarks.folders.create' ); + } + Session::flash( 'success', 'Your Folder has been created.' ); + Redirect::to( 'bookmarks/folders' ); + } + + public function editFolder( $id = null ) { + self::$title = 'Edit Folder - {SITENAME}'; + $folder = self::$folders->findById( $id ); + + if ( $folder == false ) { + Issues::add( 'error', 'Folder not found.' ); + return $this->index(); + } + + if ( $folder->createdBy != App::$activeUser->ID ) { + Issues::add( 'error', 'You do not have permission to modify this folder.' ); + return $this->index(); + } + $folderID = ( false === Input::exists('folder_id') ) ? $folder->ID : Input::post('folder_id'); + + $this->setFolderSelect( $folderID ); + Components::set( 'color', $folder->color ); + + if ( ! Input::exists( 'submit' ) ) { + return Views::view( 'bookmarks.folders.edit', $folder ); + } + + if ( !Forms::check( 'editFolder' ) ) { + Issues::add( 'error', [ 'There was an error editing your folder.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.folders.edit', $folder ); + } + + $result = self::$folders->update( $id, Input::post('title'), $folderID, Input::post('description'), Input::post('color'), Input::post('privacy') ); + if ( !$result ) { + Issues::add( 'error', [ 'There was an error updating your folder.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.folders.edit', $folder ); + } + Session::flash( 'success', 'Your Folder has been updated.' ); + Redirect::to( 'bookmarks/folders/'. $folder->ID ); + } + + public function deleteFolder( $id = null ) { + $folder = self::$folders->findById( $id ); + if ( $folder == false ) { + Issues::add( 'error', 'Folder not found.' ); + return $this->index(); + } + if ( $folder->createdBy != App::$activeUser->ID ) { + Issues::add( 'error', 'You do not have permission to modify this folder.' ); + return $this->index(); + } + $results = self::$bookmarks->deleteByFolder( $id ); + $result = self::$folders->delete( $id ); + if ( !$result ) { + Session::flash( 'error', 'There was an error deleting the folder(s)' ); + } else { + Session::flash( 'success', 'Folder deleted' ); + } + Redirect::to( 'bookmarks/folders' ); + } + + /** + * 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 ) ) { + foreach ( $folders as &$folder ) { + $folder->selected = ''; + } + } + + $linkSelect = Views::simpleView( 'bookmarks.components.linkSelect', $folders ); + Components::set( 'LINK_SELECT', $linkSelect ); + + if ( ! Input::exists( 'submit' ) ) { + return Views::view( 'bookmarks.dashboards.create' ); + } + + if ( !Forms::check( 'createDashboard' ) ) { + Issues::add( 'error', [ 'There was an error creating your dashboard.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.dashboards.create' ); + } + + if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) { + $filters = implode( ',', Input::post('link_filter') ); + } else { + $filters = ''; + } + if ( is_array( Input::post('link_order') ) && ! empty( Input::post('link_order') ) ) { + $folders = implode( ',', Input::post('link_order') ); + } else { + $folders = ''; + } + $result = self::$dashboards->create( Input::post('title'), $filters, $folders, Input::post('description') ); + + if ( !$result ) { + Issues::add( 'error', [ 'There was an error creating your dashboard.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.dashboards.create' ); + } + + Issues::add( 'success', 'Your dashboard has been created.' ); + return $this->dashboards(); + } + + 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 ) { + Issues::add( 'error', 'Unknown Dashboard' ); + return $this->dashboards(); + } + + if ( $dash->createdBy != App::$activeUser->ID ) { + Issues::add( 'error', 'You do not have permission to view this dashboard.' ); + return $this->dashboards(); + } + + $this->setDashToggles( explode( ',', $dash->saved_prefs ) ); + + $folders = self::$folders->byUser() ?? []; + $selectedFolders = explode( ',', $dash->link_order ); + if ( !empty( $folders ) ) { + foreach ( $folders as &$folder ) { + if ( in_array( $folder->ID, $selectedFolders ) ) { + $folder->selected = ' checked'; + } else { + $folder->selected = ''; + } + } + } + + $linkSelect = Views::simpleView( 'bookmarks.components.linkSelect', $folders ); + Components::set( 'LINK_SELECT', $linkSelect ); + + if ( ! Input::exists( 'submit' ) ) { + return Views::view( 'bookmarks.dashboards.edit', $dash ); + } + + if ( ! Forms::check( 'editDashboard' ) ) { + Issues::add( 'error', [ 'There was an error editing your dashboard.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.dashboards.edit', $dash ); + } + + if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) { + $filters = implode( ',', Input::post('link_filter') ); + } else { + $filters = ''; + } + + if ( is_array( Input::post('link_order') ) && ! empty( Input::post('link_order') ) ) { + $folders = implode( ',', Input::post('link_order') ); + } else { + $folders = ''; + } + + $result = self::$dashboards->update( $id, Input::post('title'), $filters, $folders, Input::post('description') ); + + if ( !$result ) { + Issues::add( 'error', [ 'There was an error updating your dashboard.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.dashboards.edit', $dash ); + } + + Issues::add( 'success', 'Your dashboard has been updated.' ); + return $this->dashboards(); + } + + 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' ); + return $this->dashboards(); + } + if ( $dash->createdBy != App::$activeUser->ID ) { + Issues::add( 'error', 'You do not have permission to delete this dash.' ); + return $this->dashboards(); + } + $result = self::$dashboards->delete( $id ); + if ( !$result ) { + Issues::add( 'error', 'There was an error deleting the dashboard(s)' ); + } else { + Issues::add( 'success', 'Dashboard deleted' ); + } + return $this->dashboards(); + } + + 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(); + } + if ( $dash->createdBy != App::$activeUser->ID ) { + 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 ) ) { + $folders = explode( ',', $dash->link_order ); + foreach ( $folders as $key => $id ) { + $folder = self::$folders->findById( $id ); + if ( empty( $folder ) ) { + continue; + } + + $bookmarks = self::$bookmarks->byFolder( $folder->ID ); + if ( empty( $bookmarks ) ) { + continue; + } + + $folderObject = new \stdClass(); + $folderObject->ID = $folder->ID; + $folderObject->title = $folder->title; + $folderObject->color = $folder->color; + $folderObject->uuid = $folder->uuid; + $folderObject->bookmarkRows = Views::simpleView( 'bookmarks.dashboards.bookmarkRows', $bookmarks ); + $foldersArray[] = $folderObject; + } + } + Components::set( 'folderPanels', Views::simpleView( 'bookmarks.dashboards.folderPanels', $foldersArray ) ); + + 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 will need an active subscription to start creating dashboards. You can check our Pricing page for more details.' ); + return Views::view( 'bookmarks.dashboardExplainer' ); + } + $dashboards = self::$dashboards->byUser(); + return Views::view( 'bookmarks.dashboards.list', $dashboards ); + } + + /** + * Functionality + */ + public function publish( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + self::$bookmarks->publish( $id ); + Session::flash( 'success', 'Bookmark mad Public.' ); + return Redirect::to( 'bookmarks/share' ); + } + + public function retract( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + self::$bookmarks->retract( $id ); + Session::flash( 'success', 'Bookmark made Private.' ); + return Redirect::to( 'bookmarks/share' ); + } + + public function hideBookmark( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + self::$bookmarks->hide( $id ); + Session::flash( 'success', 'Bookmark hidden.' ); + return Redirect::to( 'bookmarks/index' ); + } + + public function archiveBookmark( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + self::$bookmarks->archive( $id ); + Session::flash( 'success', 'Bookmark archived.' ); + return Redirect::to( 'bookmarks/index' ); + } + + public function showBookmark( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + self::$bookmarks->show( $id ); + Session::flash( 'success', 'Bookmark shown.' ); + return Redirect::to( 'bookmarks/index' ); + } + + public function unarchiveBookmark( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + self::$bookmarks->unarchive( $id ); + Session::flash( 'success', 'Bookmark un-archived.' ); + return Redirect::to( 'bookmarks/index' ); + } + + public function refreshBookmark( $id = null ) { + $bookmark = self::$bookmarks->findById( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Bookmark not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Session::flash( 'error', 'You do not have permission to modify this bookmark.' ); + return Redirect::to( 'bookmarks/index' ); + } + $info = self::$bookmarks->refreshInfo( $id ); + if ( false == $info ) { + Session::flash( 'error', 'Issue refreshing your bookmark.' ); + return Redirect::to( 'bookmarks/bookmark/' . $bookmark->ID ); + } + Session::flash( 'success', 'Bookmark data refreshed.' ); + return Redirect::to( 'bookmarks/bookmark/' . $bookmark->ID ); + } + + public function import() { + self::$title = 'Bookmark Import - {SITENAME}'; + if ( !App::$isMember ) { + Issues::add( 'notice', 'You will need an active subscription to start importing bookmarks. You can check our Pricing page for more details.' ); + return Views::view( 'bookmarks.importExplainer' ); + } + + if ( ! Input::exists('submit') ) { + return Views::view( 'bookmarks.import' ); + } + + if ( ! Forms::check( 'importBookmarks' ) ) { + Issues::add( 'error', [ 'There was an error importing your bookmarks.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.import' ); + } + + if ( isset( $_FILES['bookmark_file'] ) ) { + $file = $_FILES['bookmark_file']; + + if ($file['size'] > 1024 * 1024) { // 1024 KB = 1 MB + Issues::add( 'error', 'There is a 1 meg limit on bookmark imports at this time.' ); + return Views::view( 'bookmarks.import' ); + } + + $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if ($fileExtension !== 'html') { + Issues::add( 'error', 'Invalid file type. Only .html files are allowed.' ); + return Views::view( 'bookmarks.import' ); + } + + $fileContent = file_get_contents($file['tmp_name']); + } else { + Issues::add( 'error', 'No Import detected' ); + return Views::view( 'bookmarks.import' ); + } + + $out = $this->parseBookmarks($fileContent); + $date = date('F j, Y'); + $description = 'Imported on ' . $date . ' from file: ' . $file['name']; + + $importFolder = self::$folders->create( 'New Import', 0, $description ); + foreach ($out as $folder => $bookmarks) { + $currentFolder = self::$folders->create( $folder, $importFolder, $description ); + foreach ($bookmarks as $index => $bookmark) { + self::$bookmarks->create( $bookmark['name'], $bookmark['url'], $currentFolder); + } + } + + Session::flash( 'success', 'Your Bookmark has been created.' ); + Redirect::to( 'bookmarks/bookmarks/'. $importFolder ); + } + + public function export() { + self::$title = 'Bookmark Export - {SITENAME}'; + if ( !App::$isMember ) { + Issues::add( 'notice', 'You will need an active subscription to start exporting bookmarks. You can check our Pricing page for more details.' ); + return Views::view( 'bookmarks.exportExplainer' ); + } + + $folders = self::$folders->byUser(); + + if ( ! Input::exists('submit') ) { + return Views::view( 'bookmarks.export', $folders ); + } + + if ( ! Forms::check( 'exportBookmarks' ) ) { + Issues::add( 'error', [ 'There was an error exporting your bookmarks.' => Check::userErrors() ] ); + return Views::view( 'bookmarks.export', $folders ); + } + + $htmlDoc = ''; + $htmlDoc .= '' . PHP_EOL; + $htmlDoc .= '' . PHP_EOL; + $htmlDoc .= '' . PHP_EOL; + $htmlDoc .= '
'.$title.'
' . PHP_EOL; + $htmlDoc .= '- '.$title.'' . PHP_EOL;
+ return $htmlDoc;
+ }
+
+ private function parseBookmarks($htmlContent) {
+ $started = false;
+ $out = [];
+ $currentFolder = [];
+ $folderName = ['unknown'];
+ $lines = explode("\n", $htmlContent);
+ foreach ($lines as $line) {
+ if ( $started == false ) {
+ if (preg_match("/
(.*?)<\/h1>/i", $line, $matches)) { + $started = true; + } + continue; + } + + if (preg_match('/
/i', $line, $matches)) { + continue; + } + + if (preg_match('/
(.*?)<\/h3>/i', $line, $matches)) { + $newFolder = $matches[2]; + $out[$newFolder] = []; + array_unshift($folderName, $newFolder ); + continue; + } + + if (preg_match('/<\/DL>
/i', $line, $matches)) { + array_shift($folderName); + continue; + } + + if (preg_match('/(.*?)<\/A>/i', $line, $matches)) { + $href = $matches[1]; + $guts = $matches[2]; + $text = $matches[3]; + $added = ''; + $icon = ''; + + if (preg_match('/ADD_DATE="(.*?)"/i', $guts, $addMatches)) { + $added = $addMatches[1]; + } + if (preg_match('/ICON="(.*?)"/i', $guts, $iconMatches)) { + $icon = $iconMatches[1]; + } + + $currentFolder = $folderName[0]; + $out[$currentFolder][] = [ + 'name' => $text, + 'url' => $href, + 'addDate' => $added, + 'icon' => $icon, + 'folderName' => $folderName[0], + ]; + continue; + } + } + return $out; + } + + private function setFolderSelect( $folderID ) { + $options = self::$folders->simpleByUser(); + $out = ''; + $out .= '
'; + $out .= ''; + $out .= ''; + $folderSelect = Template::parse( $out ); + Components::set( 'folderSelect', $folderSelect ); + } + + private function setPrefToggles() { + $prefsArray = [ + 'editModeSwitch', + 'showArchivedSwitch', + 'showHiddenSwitch', + 'archiveButtonSwitch', + 'visibilityButtonSwitch', + 'privacyButtonSwitch', + 'shareButtonSwitch', + 'addButtonSwitch' + ]; + foreach ($prefsArray as $key => $name) { + if ( empty( App::$activeUser->prefs[$name] ) ) { + Components::set( $name . '_IS_CHECKED', '' ); + } else { + Components::set( $name . '_IS_CHECKED', ' checked' ); + } + } + } + + private function setDashToggles( $current = [] ) { + $prefsArray = [ + 'editModeSwitch', + 'showArchivedSwitch', + 'showHiddenSwitch', + 'archiveButtonSwitch', + 'visibilityButtonSwitch', + 'privacyButtonSwitch', + 'shareButtonSwitch', + 'addButtonSwitch' + ]; + foreach ( $prefsArray as $key => $name ) { + Debug::error( $name ); + Debug::error( $current ); + if ( ! in_array( $name, $current ) ) { + Components::set( $name . '_IS_CHECKED', '' ); + } else { + Debug::error( '_IS_CHECKED' ); + Components::set( $name . '_IS_CHECKED', ' checked' ); + } + } + } +} diff --git a/app/plugins/bookmarks/controllers/extensions.php b/app/plugins/bookmarks/controllers/extensions.php new file mode 100644 index 0000000..53b47af --- /dev/null +++ b/app/plugins/bookmarks/controllers/extensions.php @@ -0,0 +1,148 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers; + +use TheTempusProject\Classes\Controller; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Houdini\Classes\Issues; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Hermes\Functions\Route as Routes; +use TheTempusProject\Models\Token; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Models\Folders; +use TheTempusProject\Houdini\Classes\Template; +use TheTempusProject\Bedrock\Functions\Input; + +class Extensions extends Controller { + public function index() { + self::$title = 'Browser Extensions'; + self::$pageDescription = 'Our extensions cover all major browsers with the notable exception of Safari. Chrome, Opera, Brave, Firefox, and Edge are all represented; giving users a simple way to add bookmarks and folders from any page.'; + if ( App::$isLoggedIn ) { + Issues::add( 'success', 'We also have a simple solution to using the app from your mobile devices here.' ); + } + Views::view( 'bookmarks.extensions.index' ); + } + + public function mobile() { + self::$title = 'Mobile Bookmarklet'; + self::$pageDescription = 'When you find yourself on the go, sometimes an extension isn\'t an option. For those times, we have the mobile bookmarklet to allow you to add bookmarks from your mobile devices.'; + + if ( ! App::$isLoggedIn ) { + Issues::add( 'notice', 'Since the bookmarklet is tied directly to your account, you will need to log in to generate the code for your account.' ); + return Views::view( 'bookmarks.extensions.bookmarkletExplainer' ); + } + + if ( Input::exists('privacy') ) { + $privacy = 'const privacy = "' . Input::post('privacy') . '";'; + } else { + $privacy = 'const privacy = prompt("Enter privacy level (e.g., public/private):");'; + } + Components::set( 'BK_JS_PRIVACY', $privacy ); + + if ( Input::exists('includeDescription') ) { + $description = 'const notes = prompt("Enter a description (optional):");'; + } else { + $description = ''; + } + Components::set( 'BK_JS_NOTES', $description ); + + if ( Input::exists('includeColor') ) { + $color = 'const color = "'.Input::post('color').'";'; + } else { + $color = ''; + } + Components::set( 'BK_JS_COLOR', $color ); + + if ( Input::exists('includeFolder') ) { + $folder = 'const folder = "'.Input::post('folder_id').'";'; + } else { + $folder = ''; + } + Components::set( 'BK_JS_FOLDER', $folder ); + + $this->setFolderSelect(Input::post('folder')); + + $tokens = new Token; + $apiKey = $tokens->findOrCreateUserToken( App::$activeUser->ID, true ); + $apiUrl = Routes::getAddress() . 'api/bookmarks/create'; + Components::set( 'BK_API_KEY', $apiKey ); + Components::set( 'BK_API_URL', $apiUrl ); + + Views::view( 'bookmarks.extensions.bookmarklet' ); + } + + public function chrome() { + self::$title = 'Chrome Extension'; + self::$pageDescription = 'Our Chrome extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.'; + Views::view( 'bookmarks.extensions.chrome' ); + } + + public function firefox() { + self::$title = 'Firefox Extension'; + self::$pageDescription = 'Our Firefox extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.'; + Views::view( 'bookmarks.extensions.firefox' ); + } + + public function opera() { + self::$title = 'Opera Extension'; + self::$pageDescription = 'Our Opera extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.'; + Views::view( 'bookmarks.extensions.opera' ); + } + + public function edge() { + self::$title = 'Edge Extension'; + self::$pageDescription = 'Our Edge extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.'; + Views::view( 'bookmarks.extensions.edge' ); + } + + public function brave() { + self::$title = 'Brave Extension'; + self::$pageDescription = 'Our Brave extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.'; + Views::view( 'bookmarks.extensions.brave' ); + } + + public function safari() { + self::$title = 'Safari Extension'; + self::$pageDescription = 'Our Safari extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.'; + Views::view( 'bookmarks.extensions.safari' ); + } + + private function setFolderSelect( $folderID = null ) { + $folders = new Folders; + $options = $folders->simpleByUser(); + $out = ''; + $out .= ''; + $folderSelect = Template::parse( $out ); + Components::set( 'folderSelect', $folderSelect ); + } +} diff --git a/app/plugins/bookmarks/controllers/shared.php b/app/plugins/bookmarks/controllers/shared.php new file mode 100644 index 0000000..322c709 --- /dev/null +++ b/app/plugins/bookmarks/controllers/shared.php @@ -0,0 +1,107 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers; + +use TheTempusProject\Hermes\Functions\Redirect; +use TheTempusProject\Bedrock\Functions\Session; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Classes\Controller; +use TheTempusProject\Models\Bookmarks as Bookmark; +use TheTempusProject\Models\Folders; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Hermes\Functions\Route as Routes; + +class Shared extends Controller { + protected static $bookmarks; + protected static $folders; + + public function __construct() { + parent::__construct(); + self::$bookmarks = new Bookmark; + self::$folders = new Folders; + self::$title = 'Bookmarks - {SITENAME}'; + self::$pageDescription = 'Add and save url bookmarks here.'; + Components::set( 'SITE_URL', Routes::getAddress() ); + } + + public function shared( $type = '', $id = '' ) { + if ( empty( $type ) ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + + if ( empty( $id ) ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + + $type = strtolower( $type ); + if ( ! in_array( $type, ['link','folder'] ) ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + + $this->$type( $id ); + } + + public function link( $id = '' ) { + if ( empty( $id ) ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + $bookmark = self::$bookmarks->findByUuid( $id ); + if ( $bookmark == false ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + if ( $bookmark->privacy == 'private' ) { + if ( empty( $bookmark->folderID ) ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + $folder = self::$folders->findByUuid( $bookmark->folderID ); + if ( $folder == false ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + if ( $folder->privacy == 'private' ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + } + } + return Views::view( 'bookmarks.public.bookmark', $bookmark ); + } + + public function folder( $id = '' ) { + if ( empty( $id ) ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + $folder = self::$folders->findByUuid( $id ); + if ( $folder == false ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + if ( $folder->privacy == 'private' ) { + Session::flash( 'error', 'Unknown share' ); + return Redirect::to( 'home/index' ); + } + $folder->bookmarks = self::$bookmarks->unsafeByFolder( $folder->ID ); + $folder->bookmarkListRows = Views::simpleView( 'bookmarks.components.publicListRows', $folder->bookmarks ); + $folder->panel = Views::simpleView( 'bookmarks.components.publicList', [$folder] ); + return Views::view( 'bookmarks.public.folder', $folder ); + } +} diff --git a/app/plugins/bookmarks/controllers/tutorials.php b/app/plugins/bookmarks/controllers/tutorials.php new file mode 100644 index 0000000..a058562 --- /dev/null +++ b/app/plugins/bookmarks/controllers/tutorials.php @@ -0,0 +1,114 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers; + +use TheTempusProject\Houdini\Classes\Issues; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Classes\Controller; +use TheTempusProject\Houdini\Classes\Navigation; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Bedrock\Functions\Input; + +class Tutorials extends Controller { + public function __construct() { + parent::__construct(); + self::$title = 'Tutorials - {SITENAME}'; + self::$pageDescription = 'We have detailed walkthroughs on how to perform a number of tasks for every major browser.'; + } + + public function index( $browser = '', $tutorial = '' ) { + return Views::view( 'bookmarks.tutorials.list' ); + } + + public function brave( $tutorial = '' ) { + Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) ); + + if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) { + $test = new \stdClass(); + $test->pretty = 'Brave'; + $test->printed = 'brave'; + return Views::view( 'bookmarks.tutorials.card', [ $test ] ); + } + return Views::view( 'bookmarks.tutorials.brave.' . $tutorial ); + } + + public function chrome( $tutorial = '' ) { + Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) ); + + if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) { + $test = new \stdClass(); + $test->pretty = 'Chrome'; + $test->printed = 'chrome'; + return Views::view( 'bookmarks.tutorials.card', [ $test ] ); + } + + return Views::view( 'bookmarks.tutorials.chrome.' . $tutorial ); + } + + public function edge( $tutorial = '' ) { + Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) ); + + if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) { + $test = new \stdClass(); + $test->pretty = 'Edge'; + $test->printed = 'edge'; + return Views::view( 'bookmarks.tutorials.card', [ $test ] ); + } + return Views::view( 'bookmarks.tutorials.edge.' . $tutorial ); + } + + public function opera( $tutorial = '' ) { + Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) ); + + if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) { + $test = new \stdClass(); + $test->pretty = 'Opera'; + $test->printed = 'opera'; + return Views::view( 'bookmarks.tutorials.card', [ $test ] ); + } + return Views::view( 'bookmarks.tutorials.opera.' . $tutorial ); + } + + public function firefox( $tutorial = '' ) { + Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) ); + + if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) { + $test = new \stdClass(); + $test->pretty = 'Firefox'; + $test->printed = 'firefox'; + return Views::view( 'bookmarks.tutorials.card', [ $test ] ); + } + return Views::view( 'bookmarks.tutorials.firefox.' . $tutorial ); + } + + public function safari( $tutorial = '' ) { + Issues::add( 'notice', 'Safari is not supported at this time.' ); + return; + if ( ! in_array( $tutorial, ['pin','settings','import','export'] ) ) { + Issues::add( 'notice', 'Unknown tutorial' ); + return Views::view( 'bookmarks.tutorials.list' ); + } + return Views::view( 'bookmarks.tutorials.safari.' . $tutorial ); + } + + public function mobile( $tutorial = '' ) { + Navigation::setCrumbComponent( 'tutorialCrumbs', Input::get( 'url' ) ); + if ( ! in_array( $tutorial, ['iphone','android'] ) ) { + $test = new \stdClass(); + $test->pretty = 'Mobile'; + $test->printed = 'mobile'; + return Views::view( 'bookmarks.tutorials.mobileCard', [ $test ] ); + } + return Views::view( 'bookmarks.tutorials.mobile.' . $tutorial ); + } +} \ No newline at end of file diff --git a/app/plugins/bookmarks/forms.php b/app/plugins/bookmarks/forms.php new file mode 100644 index 0000000..a76a903 --- /dev/null +++ b/app/plugins/bookmarks/forms.php @@ -0,0 +1,225 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Plugins\Bookmarks; + +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Classes\Forms; + +class BookmarksForms extends Forms { + /** + * Adds these functions to the form list. + */ + public function __construct() { + self::addHandler( 'createBookmark', __CLASS__, 'createBookmark' ); + self::addHandler( 'createFolder', __CLASS__, 'createFolder' ); + self::addHandler( 'editBookmark', __CLASS__, 'editBookmark' ); + self::addHandler( 'editFolder', __CLASS__, 'editFolder' ); + self::addHandler( 'importBookmarks', __CLASS__, 'importBookmarks' ); + self::addHandler( 'exportBookmarks', __CLASS__, 'exportBookmarks' ); + self::addHandler( 'createDashboard', __CLASS__, 'createDashboard' ); + self::addHandler( 'editDashboard', __CLASS__, 'editDashboard' ); + self::addHandler( 'updateDashboard', __CLASS__, 'updateDashboard' ); + } + + public static function createBookmark() { + // if ( ! Input::exists( 'title' ) ) { + // Check::addUserError( 'You must include a title.' ); + // return false; + // } + if ( ! Input::exists( 'url' ) ) { + Check::addUserError( 'You must include a url.' ); + return false; + } + // if ( ! Input::exists( 'color' ) ) { + // Check::addUserError( 'You must include a color.' ); + // return false; + // } + // if ( ! Input::exists( 'privacy' ) ) { + // Check::addUserError( 'You must include a privacy.' ); + // return false; + // } + // if ( !self::token() ) { + // Check::addUserError( 'token - comment out later.' ); + // return false; + // } + return true; + } + + public static function createFolder() { + if ( ! Input::exists( 'title' ) ) { + Check::addUserError( 'You must include a title.' ); + return false; + } + // if ( ! Input::exists( 'color' ) ) { + // Check::addUserError( 'You must include a color.' ); + // return false; + // } + // if ( ! Input::exists( 'privacy' ) ) { + // Check::addUserError( 'You must include a privacy.' ); + // return false; + // } + // if ( ! self::token() ) { + // Check::addUserError( 'token - comment out later.' ); + // return false; + // } + return true; + } + + public static function editBookmark() { + // if ( ! Input::exists( 'title' ) ) { + // Check::addUserError( 'You must include a title.' ); + // return false; + // } + if ( ! Input::exists( 'url' ) ) { + Check::addUserError( 'You must include a url.' ); + return false; + } + // if ( ! Input::exists( 'color' ) ) { + // Check::addUserError( 'You must include a color.' ); + // return false; + // } + // if ( ! Input::exists( 'privacy' ) ) { + // Check::addUserError( 'You must include a privacy.' ); + // return false; + // } + // if ( !self::token() ) { + // Check::addUserError( 'token - comment out later.' ); + // return false; + // } + return true; + } + + public static function editFolder() { + if ( ! Input::exists( 'submit' ) ) { + return false; + } + if ( ! Input::exists( 'title' ) ) { + Check::addUserError( 'You must include a title.' ); + return false; + } + // if ( ! Input::exists( 'color' ) ) { + // Check::addUserError( 'You must include a color.' ); + // return false; + // } + // if ( ! Input::exists( 'privacy' ) ) { + // Check::addUserError( 'You must include a privacy.' ); + // return false; + // } + // if ( !self::token() ) { + // Check::addUserError( 'token - comment out later.' ); + // return false; + // } + return true; + } + + public static function importBookmarks() { + if ( ! Input::exists( 'submit' ) ) { + return false; + } + // if ( ! Input::exists( 'title' ) ) { + // Check::addUserError( 'You must include a title.' ); + // return false; + // } + // if ( ! Input::exists( 'color' ) ) { + // Check::addUserError( 'You must include a color.' ); + // return false; + // } + // if ( ! Input::exists( 'privacy' ) ) { + // Check::addUserError( 'You must include a privacy.' ); + // return false; + // } + // if ( !self::token() ) { + // Check::addUserError( 'token - comment out later.' ); + // return false; + // } + return true; + } + + public static function exportBookmarks() { + if ( ! Input::exists( 'submit' ) ) { + return false; + } + if ( ! Input::exists( 'BF_' ) ) { + return false; + } + // if ( ! Input::exists( 'title' ) ) { + // Check::addUserError( 'You must include a title.' ); + // return false; + // } + // if ( ! Input::exists( 'color' ) ) { + // Check::addUserError( 'You must include a color.' ); + // return false; + // } + // if ( ! Input::exists( 'privacy' ) ) { + // Check::addUserError( 'You must include a privacy.' ); + // return false; + // } + // if ( !self::token() ) { + // Check::addUserError( 'token - comment out later.' ); + // return false; + // } + return true; + } + + public static function createDashboard() { + if ( ! Input::exists( 'submit' ) ) { + return false; + } + if ( ! Input::exists( 'title' ) ) { + Check::addUserError( 'You must include a title.' ); + return false; + } + if ( ! Input::exists( 'link_order' ) ) { + Check::addUserError( 'You must include at least 1 link or folder.' ); + return false; + } + if ( !self::token() ) { + Check::addUserError( 'There was an issue with your request.' ); + return false; + } + 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; + } + if ( ! Input::exists( 'title' ) ) { + Check::addUserError( 'You must include a title.' ); + return false; + } + if ( ! Input::exists( 'link_order' ) ) { + Check::addUserError( 'You must include at least 1 link or folder.' ); + return false; + } + if ( !self::token() ) { + Check::addUserError( 'There was an issue with your request.' ); + return false; + } + return true; + } +} + +new BookmarksForms; \ No newline at end of file diff --git a/app/plugins/bookmarks/images/android/add.png b/app/plugins/bookmarks/images/android/add.png new file mode 100644 index 0000000..df8c241 Binary files /dev/null and b/app/plugins/bookmarks/images/android/add.png differ diff --git a/app/plugins/bookmarks/images/android/add2.png b/app/plugins/bookmarks/images/android/add2.png new file mode 100644 index 0000000..649bdb8 Binary files /dev/null and b/app/plugins/bookmarks/images/android/add2.png differ diff --git a/app/plugins/bookmarks/images/android/add3.png b/app/plugins/bookmarks/images/android/add3.png new file mode 100644 index 0000000..0a9eecd Binary files /dev/null and b/app/plugins/bookmarks/images/android/add3.png differ diff --git a/app/plugins/bookmarks/images/android/added.png b/app/plugins/bookmarks/images/android/added.png new file mode 100644 index 0000000..f81c901 Binary files /dev/null and b/app/plugins/bookmarks/images/android/added.png differ diff --git a/app/plugins/bookmarks/images/brave/bookmark-manager.png b/app/plugins/bookmarks/images/brave/bookmark-manager.png new file mode 100644 index 0000000..3024811 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/bookmark-manager.png differ diff --git a/app/plugins/bookmarks/images/brave/bookmark-menu.png b/app/plugins/bookmarks/images/brave/bookmark-menu.png new file mode 100644 index 0000000..b6d3309 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/bookmark-menu.png differ diff --git a/app/plugins/bookmarks/images/brave/bookmarks.png b/app/plugins/bookmarks/images/brave/bookmarks.png new file mode 100644 index 0000000..b431cbe Binary files /dev/null and b/app/plugins/bookmarks/images/brave/bookmarks.png differ diff --git a/app/plugins/bookmarks/images/brave/export.png b/app/plugins/bookmarks/images/brave/export.png new file mode 100644 index 0000000..87dd1c6 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/export.png differ diff --git a/app/plugins/bookmarks/images/brave/extensions-icon.png b/app/plugins/bookmarks/images/brave/extensions-icon.png new file mode 100644 index 0000000..bcc2cc1 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/extensions-icon.png differ diff --git a/app/plugins/bookmarks/images/brave/import.png b/app/plugins/bookmarks/images/brave/import.png new file mode 100644 index 0000000..622053c Binary files /dev/null and b/app/plugins/bookmarks/images/brave/import.png differ diff --git a/app/plugins/bookmarks/images/brave/options.png b/app/plugins/bookmarks/images/brave/options.png new file mode 100644 index 0000000..10b9339 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/options.png differ diff --git a/app/plugins/bookmarks/images/brave/pin.png b/app/plugins/bookmarks/images/brave/pin.png new file mode 100644 index 0000000..8e800f0 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/pin.png differ diff --git a/app/plugins/bookmarks/images/brave/pinned.png b/app/plugins/bookmarks/images/brave/pinned.png new file mode 100644 index 0000000..f94fb59 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/pinned.png differ diff --git a/app/plugins/bookmarks/images/brave/settings-page.png b/app/plugins/bookmarks/images/brave/settings-page.png new file mode 100644 index 0000000..f1671dc Binary files /dev/null and b/app/plugins/bookmarks/images/brave/settings-page.png differ diff --git a/app/plugins/bookmarks/images/brave/settings.png b/app/plugins/bookmarks/images/brave/settings.png new file mode 100644 index 0000000..668b048 Binary files /dev/null and b/app/plugins/bookmarks/images/brave/settings.png differ diff --git a/app/plugins/bookmarks/images/chrome/bookmark-manager.png b/app/plugins/bookmarks/images/chrome/bookmark-manager.png new file mode 100644 index 0000000..1dd091a Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/bookmark-manager.png differ diff --git a/app/plugins/bookmarks/images/chrome/bookmark-menu.png b/app/plugins/bookmarks/images/chrome/bookmark-menu.png new file mode 100644 index 0000000..f559010 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/bookmark-menu.png differ diff --git a/app/plugins/bookmarks/images/chrome/bookmarks.png b/app/plugins/bookmarks/images/chrome/bookmarks.png new file mode 100644 index 0000000..def6eb6 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/bookmarks.png differ diff --git a/app/plugins/bookmarks/images/chrome/chrome-add.jpg b/app/plugins/bookmarks/images/chrome/chrome-add.jpg new file mode 100644 index 0000000..579d5b8 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/chrome-add.jpg differ diff --git a/app/plugins/bookmarks/images/chrome/chrome-login.jpg b/app/plugins/bookmarks/images/chrome/chrome-login.jpg new file mode 100644 index 0000000..db42731 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/chrome-login.jpg differ diff --git a/app/plugins/bookmarks/images/chrome/chrome-marquee-promo-tile.png b/app/plugins/bookmarks/images/chrome/chrome-marquee-promo-tile.png new file mode 100644 index 0000000..3c9da7f Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/chrome-marquee-promo-tile.png differ diff --git a/app/plugins/bookmarks/images/chrome/chrome-settings.jpg b/app/plugins/bookmarks/images/chrome/chrome-settings.jpg new file mode 100644 index 0000000..97949b7 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/chrome-settings.jpg differ diff --git a/app/plugins/bookmarks/images/chrome/chrome-small-promo-tile.png b/app/plugins/bookmarks/images/chrome/chrome-small-promo-tile.png new file mode 100644 index 0000000..015a2f2 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/chrome-small-promo-tile.png differ diff --git a/app/plugins/bookmarks/images/chrome/export.png b/app/plugins/bookmarks/images/chrome/export.png new file mode 100644 index 0000000..42d4fe1 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/export.png differ diff --git a/app/plugins/bookmarks/images/chrome/extensions-icon.png b/app/plugins/bookmarks/images/chrome/extensions-icon.png new file mode 100644 index 0000000..34dfd50 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/extensions-icon.png differ diff --git a/app/plugins/bookmarks/images/chrome/import.png b/app/plugins/bookmarks/images/chrome/import.png new file mode 100644 index 0000000..9d6cb93 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/import.png differ diff --git a/app/plugins/bookmarks/images/chrome/options.png b/app/plugins/bookmarks/images/chrome/options.png new file mode 100644 index 0000000..9fa20a6 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/options.png differ diff --git a/app/plugins/bookmarks/images/chrome/pin.png b/app/plugins/bookmarks/images/chrome/pin.png new file mode 100644 index 0000000..eb90300 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/pin.png differ diff --git a/app/plugins/bookmarks/images/chrome/pinned.png b/app/plugins/bookmarks/images/chrome/pinned.png new file mode 100644 index 0000000..7f21ba0 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/pinned.png differ diff --git a/app/plugins/bookmarks/images/chrome/settings-page.png b/app/plugins/bookmarks/images/chrome/settings-page.png new file mode 100644 index 0000000..e662028 Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/settings-page.png differ diff --git a/app/plugins/bookmarks/images/chrome/settings.png b/app/plugins/bookmarks/images/chrome/settings.png new file mode 100644 index 0000000..ba7f40b Binary files /dev/null and b/app/plugins/bookmarks/images/chrome/settings.png differ diff --git a/app/plugins/bookmarks/images/edge/bookmark-options.png b/app/plugins/bookmarks/images/edge/bookmark-options.png new file mode 100644 index 0000000..f6b0743 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/bookmark-options.png differ diff --git a/app/plugins/bookmarks/images/edge/edge-add.png b/app/plugins/bookmarks/images/edge/edge-add.png new file mode 100644 index 0000000..547cf55 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/edge-add.png differ diff --git a/app/plugins/bookmarks/images/edge/edge-login.png b/app/plugins/bookmarks/images/edge/edge-login.png new file mode 100644 index 0000000..7e27a1c Binary files /dev/null and b/app/plugins/bookmarks/images/edge/edge-login.png differ diff --git a/app/plugins/bookmarks/images/edge/edge-settings.png b/app/plugins/bookmarks/images/edge/edge-settings.png new file mode 100644 index 0000000..db02594 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/edge-settings.png differ diff --git a/app/plugins/bookmarks/images/edge/edge-small-promo-tile.png b/app/plugins/bookmarks/images/edge/edge-small-promo-tile.png new file mode 100644 index 0000000..015a2f2 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/edge-small-promo-tile.png differ diff --git a/app/plugins/bookmarks/images/edge/export.png b/app/plugins/bookmarks/images/edge/export.png new file mode 100644 index 0000000..69d71fb Binary files /dev/null and b/app/plugins/bookmarks/images/edge/export.png differ diff --git a/app/plugins/bookmarks/images/edge/extensions-icon.png b/app/plugins/bookmarks/images/edge/extensions-icon.png new file mode 100644 index 0000000..3d18c16 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/extensions-icon.png differ diff --git a/app/plugins/bookmarks/images/edge/favorites.png b/app/plugins/bookmarks/images/edge/favorites.png new file mode 100644 index 0000000..f45bf3e Binary files /dev/null and b/app/plugins/bookmarks/images/edge/favorites.png differ diff --git a/app/plugins/bookmarks/images/edge/import-complete.png b/app/plugins/bookmarks/images/edge/import-complete.png new file mode 100644 index 0000000..843469a Binary files /dev/null and b/app/plugins/bookmarks/images/edge/import-complete.png differ diff --git a/app/plugins/bookmarks/images/edge/import-menu.png b/app/plugins/bookmarks/images/edge/import-menu.png new file mode 100644 index 0000000..7d86967 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/import-menu.png differ diff --git a/app/plugins/bookmarks/images/edge/import-select.png b/app/plugins/bookmarks/images/edge/import-select.png new file mode 100644 index 0000000..ac3d844 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/import-select.png differ diff --git a/app/plugins/bookmarks/images/edge/import-start.png b/app/plugins/bookmarks/images/edge/import-start.png new file mode 100644 index 0000000..8dfb0d1 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/import-start.png differ diff --git a/app/plugins/bookmarks/images/edge/import.png b/app/plugins/bookmarks/images/edge/import.png new file mode 100644 index 0000000..c0b8a67 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/import.png differ diff --git a/app/plugins/bookmarks/images/edge/options.png b/app/plugins/bookmarks/images/edge/options.png new file mode 100644 index 0000000..2654977 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/options.png differ diff --git a/app/plugins/bookmarks/images/edge/pin.png b/app/plugins/bookmarks/images/edge/pin.png new file mode 100644 index 0000000..6890a40 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/pin.png differ diff --git a/app/plugins/bookmarks/images/edge/pinned.png b/app/plugins/bookmarks/images/edge/pinned.png new file mode 100644 index 0000000..9b685c7 Binary files /dev/null and b/app/plugins/bookmarks/images/edge/pinned.png differ diff --git a/app/plugins/bookmarks/images/edge/settings-page.png b/app/plugins/bookmarks/images/edge/settings-page.png new file mode 100644 index 0000000..2b2d9ab Binary files /dev/null and b/app/plugins/bookmarks/images/edge/settings-page.png differ diff --git a/app/plugins/bookmarks/images/edge/settings.png b/app/plugins/bookmarks/images/edge/settings.png new file mode 100644 index 0000000..68ffede Binary files /dev/null and b/app/plugins/bookmarks/images/edge/settings.png differ diff --git a/app/plugins/bookmarks/images/firefox/added.png b/app/plugins/bookmarks/images/firefox/added.png new file mode 100644 index 0000000..0c4dc64 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/added.png differ diff --git a/app/plugins/bookmarks/images/firefox/bookmarks.png b/app/plugins/bookmarks/images/firefox/bookmarks.png new file mode 100644 index 0000000..33b7050 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/bookmarks.png differ diff --git a/app/plugins/bookmarks/images/firefox/enable.png b/app/plugins/bookmarks/images/firefox/enable.png new file mode 100644 index 0000000..fc2f578 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/enable.png differ diff --git a/app/plugins/bookmarks/images/firefox/export.png b/app/plugins/bookmarks/images/firefox/export.png new file mode 100644 index 0000000..a95ca72 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/export.png differ diff --git a/app/plugins/bookmarks/images/firefox/extensions-icon.png b/app/plugins/bookmarks/images/firefox/extensions-icon.png new file mode 100644 index 0000000..f364eda Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/extensions-icon.png differ diff --git a/app/plugins/bookmarks/images/firefox/firefox-login.jpg b/app/plugins/bookmarks/images/firefox/firefox-login.jpg new file mode 100644 index 0000000..df62908 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/firefox-login.jpg differ diff --git a/app/plugins/bookmarks/images/firefox/firefox-save.jpg b/app/plugins/bookmarks/images/firefox/firefox-save.jpg new file mode 100644 index 0000000..48e3a98 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/firefox-save.jpg differ diff --git a/app/plugins/bookmarks/images/firefox/firefox-settings.jpg b/app/plugins/bookmarks/images/firefox/firefox-settings.jpg new file mode 100644 index 0000000..f2c8921 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/firefox-settings.jpg differ diff --git a/app/plugins/bookmarks/images/firefox/icon.png b/app/plugins/bookmarks/images/firefox/icon.png new file mode 100644 index 0000000..8e4f9b8 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/icon.png differ diff --git a/app/plugins/bookmarks/images/firefox/import-export.png b/app/plugins/bookmarks/images/firefox/import-export.png new file mode 100644 index 0000000..5809cc1 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/import-export.png differ diff --git a/app/plugins/bookmarks/images/firefox/import.png b/app/plugins/bookmarks/images/firefox/import.png new file mode 100644 index 0000000..de7fdf0 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/import.png differ diff --git a/app/plugins/bookmarks/images/firefox/manage-bookmarks.png b/app/plugins/bookmarks/images/firefox/manage-bookmarks.png new file mode 100644 index 0000000..c0b76a6 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/manage-bookmarks.png differ diff --git a/app/plugins/bookmarks/images/firefox/manage-page.png b/app/plugins/bookmarks/images/firefox/manage-page.png new file mode 100644 index 0000000..d8c972b Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/manage-page.png differ diff --git a/app/plugins/bookmarks/images/firefox/manage-select.png b/app/plugins/bookmarks/images/firefox/manage-select.png new file mode 100644 index 0000000..b0914de Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/manage-select.png differ diff --git a/app/plugins/bookmarks/images/firefox/manage.png b/app/plugins/bookmarks/images/firefox/manage.png new file mode 100644 index 0000000..d35d783 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/manage.png differ diff --git a/app/plugins/bookmarks/images/firefox/options.png b/app/plugins/bookmarks/images/firefox/options.png new file mode 100644 index 0000000..234ed54 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/options.png differ diff --git a/app/plugins/bookmarks/images/firefox/pin.png b/app/plugins/bookmarks/images/firefox/pin.png new file mode 100644 index 0000000..5f35ade Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/pin.png differ diff --git a/app/plugins/bookmarks/images/firefox/pinned.png b/app/plugins/bookmarks/images/firefox/pinned.png new file mode 100644 index 0000000..9f10d05 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/pinned.png differ diff --git a/app/plugins/bookmarks/images/firefox/settings-page.png b/app/plugins/bookmarks/images/firefox/settings-page.png new file mode 100644 index 0000000..c6c6368 Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/settings-page.png differ diff --git a/app/plugins/bookmarks/images/firefox/settings.png b/app/plugins/bookmarks/images/firefox/settings.png new file mode 100644 index 0000000..5af44af Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/settings.png differ diff --git a/app/plugins/bookmarks/images/firefox/version.png b/app/plugins/bookmarks/images/firefox/version.png new file mode 100644 index 0000000..ed7e94e Binary files /dev/null and b/app/plugins/bookmarks/images/firefox/version.png differ diff --git a/app/plugins/bookmarks/images/opera/bookmarks.png b/app/plugins/bookmarks/images/opera/bookmarks.png new file mode 100644 index 0000000..6d61869 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/bookmarks.png differ diff --git a/app/plugins/bookmarks/images/opera/export.png b/app/plugins/bookmarks/images/opera/export.png new file mode 100644 index 0000000..afee201 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/export.png differ diff --git a/app/plugins/bookmarks/images/opera/extensions.png b/app/plugins/bookmarks/images/opera/extensions.png new file mode 100644 index 0000000..48587bc Binary files /dev/null and b/app/plugins/bookmarks/images/opera/extensions.png differ diff --git a/app/plugins/bookmarks/images/opera/import-select.png b/app/plugins/bookmarks/images/opera/import-select.png new file mode 100644 index 0000000..ce92936 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/import-select.png differ diff --git a/app/plugins/bookmarks/images/opera/import-start.png b/app/plugins/bookmarks/images/opera/import-start.png new file mode 100644 index 0000000..75b2a2c Binary files /dev/null and b/app/plugins/bookmarks/images/opera/import-start.png differ diff --git a/app/plugins/bookmarks/images/opera/import-success.png b/app/plugins/bookmarks/images/opera/import-success.png new file mode 100644 index 0000000..e34bc70 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/import-success.png differ diff --git a/app/plugins/bookmarks/images/opera/import.png b/app/plugins/bookmarks/images/opera/import.png new file mode 100644 index 0000000..02c0046 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/import.png differ diff --git a/app/plugins/bookmarks/images/opera/opera-login.jpg b/app/plugins/bookmarks/images/opera/opera-login.jpg new file mode 100644 index 0000000..0bb1bc7 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/opera-login.jpg differ diff --git a/app/plugins/bookmarks/images/opera/opera-promo-image.png b/app/plugins/bookmarks/images/opera/opera-promo-image.png new file mode 100644 index 0000000..cd2ca2a Binary files /dev/null and b/app/plugins/bookmarks/images/opera/opera-promo-image.png differ diff --git a/app/plugins/bookmarks/images/opera/opera-save.jpg b/app/plugins/bookmarks/images/opera/opera-save.jpg new file mode 100644 index 0000000..aaa5aa6 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/opera-save.jpg differ diff --git a/app/plugins/bookmarks/images/opera/opera-settiings.jpg b/app/plugins/bookmarks/images/opera/opera-settiings.jpg new file mode 100644 index 0000000..37c4081 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/opera-settiings.jpg differ diff --git a/app/plugins/bookmarks/images/opera/optiions.png b/app/plugins/bookmarks/images/opera/optiions.png new file mode 100644 index 0000000..1e0f192 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/optiions.png differ diff --git a/app/plugins/bookmarks/images/opera/options.png b/app/plugins/bookmarks/images/opera/options.png new file mode 100644 index 0000000..1e0f192 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/options.png differ diff --git a/app/plugins/bookmarks/images/opera/pin.png b/app/plugins/bookmarks/images/opera/pin.png new file mode 100644 index 0000000..986aac1 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/pin.png differ diff --git a/app/plugins/bookmarks/images/opera/pinned.png b/app/plugins/bookmarks/images/opera/pinned.png new file mode 100644 index 0000000..4881f6c Binary files /dev/null and b/app/plugins/bookmarks/images/opera/pinned.png differ diff --git a/app/plugins/bookmarks/images/opera/settings-page.png b/app/plugins/bookmarks/images/opera/settings-page.png new file mode 100644 index 0000000..9103530 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/settings-page.png differ diff --git a/app/plugins/bookmarks/images/opera/sidebar.png b/app/plugins/bookmarks/images/opera/sidebar.png new file mode 100644 index 0000000..bc0a3d6 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/sidebar.png differ diff --git a/app/plugins/bookmarks/images/opera/toolbar.png b/app/plugins/bookmarks/images/opera/toolbar.png new file mode 100644 index 0000000..6a84ab2 Binary files /dev/null and b/app/plugins/bookmarks/images/opera/toolbar.png differ diff --git a/app/plugins/bookmarks/js/bookmarklet.js b/app/plugins/bookmarks/js/bookmarklet.js new file mode 100644 index 0000000..df5b86c --- /dev/null +++ b/app/plugins/bookmarks/js/bookmarklet.js @@ -0,0 +1,35 @@ +javascript:(function() { + const apiKey = localStorage.getItem('notAnAuthToken'); + const apiUrl = localStorage.getItem('api_url'); + const name = prompt("Enter a name for the bookmark:"); + const notes = prompt("Enter any notes (optional):"); + const color = prompt("Enter a color (optional):"); + const privacy = prompt("Enter privacy level (e.g., public/private):"); + const folder = prompt("Enter a folder (optional):"); + const url = window.location.href; + + if (!name) { + alert("Name is required."); + return; + } + + fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}` + }, + body: JSON.stringify({ name, url, notes, color, privacy, folder }) + }) + .then(response => { + if (response.ok) { + alert("Bookmark saved successfully!"); + } else { + alert("Failed to save bookmark. Please check your API key."); + } + }) + .catch(error => { + console.error(error); + alert("An unknown error occurred while saving the bookmark."); + }); +})(); diff --git a/app/plugins/bookmarks/js/bookmarks.js b/app/plugins/bookmarks/js/bookmarks.js new file mode 100644 index 0000000..b73a23c --- /dev/null +++ b/app/plugins/bookmarks/js/bookmarks.js @@ -0,0 +1,234 @@ +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)) + + // Initialize all toggle functions + toggleVisibility('editModeSwitch', 'edit-mode'); + toggleVisibility('showArchivedSwitch', 'link-archived'); + toggleVisibility('showHiddenSwitch', 'link-hidden'); + toggleVisibility('archiveButtonSwitch', 'btn-archive'); + toggleVisibility('visibilityButtonSwitch', 'btn-hideshow'); + toggleVisibility('privacyButtonSwitch', 'btn-publish'); + toggleVisibility('addButtonSwitch', 'btn-addlink'); + toggleVisibility('shareButtonSwitch', 'btn-share'); + + toggleVisibility('dashShowArchivedSwitch', 'link-archived'); + toggleVisibility('dashShowHiddenSwitch', 'link-hidden'); + toggleVisibility('dashAddButtonSwitch', 'btn-addlink'); + toggleVisibility('dashShareButtonSwitch', 'btn-share'); + + // Retrieve the list of collapsed folderCard IDs from local storage + + const onDashboard = document.getElementById("dash_id"); + let collapsedFolders; + if ( onDashboard ) { + collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedDashFolders' + onDashboard.value )) || []; + } else { + 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', () => { + let storageName; + // Add the folderCard ID to local storage when collapsed + if (!collapsedFolders.includes(folderCardId)) { + collapsedFolders.push(folderCardId); + if ( onDashboard ) { + storageName = 'collapsedDashFolders' + onDashboard.value; + } else { + storageName = 'collapsedFolders'; + } + localStorage.setItem( storageName , JSON.stringify(collapsedFolders)); + } + }); + + collapseElement.addEventListener('shown.bs.collapse', () => { + let storageName; + // Remove the folderCard ID from local storage when expanded + const index = collapsedFolders.indexOf(folderCardId); + if (index > -1) { + collapsedFolders.splice(index, 1); + if ( onDashboard ) { + storageName = 'collapsedDashFolders' + onDashboard.value; + } else { + storageName = 'collapsedFolders'; + } + localStorage.setItem( storageName , 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 +function toggleVisibility(switchId, className) { + const switchElement = document.getElementById(switchId); + const elementsToToggle = document.querySelectorAll(`.${className}`); + + if ( ! switchElement ) { + return; + } + + // Listen for changes to the checkbox + switchElement.addEventListener('change', () => { + 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')); +} + +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(",")); + } +} + +function loadDashLinkOrder() { + const onDashboard = document.getElementById("dash_id"); + const storedOrder = localStorage.getItem("manageFolderOrder"); // Get the saved order + const bookmarkSort = document.getElementById("bookmarkSort"); + + if ( onDashboard || !storedOrder || !bookmarkSort ) return; // Exit if no saved order or no container + + const orderArray = storedOrder.split(","); // Convert the saved order into an array + const bookmarkCards = Array.from(document.querySelectorAll("#bookmarkSort .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); + }); + + // 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 +} diff --git a/app/plugins/bookmarks/models/bookmark_dashboards.php b/app/plugins/bookmarks/models/bookmark_dashboards.php new file mode 100644 index 0000000..a652af9 --- /dev/null +++ b/app/plugins/bookmarks/models/bookmark_dashboards.php @@ -0,0 +1,176 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Houdini\Classes\Filters; +use TheTempusProject\Canary\Classes\CustomException; + +class BookmarkDashboards extends DatabaseModel { + public $tableName = 'bookmark_dashboards'; + public $databaseMatrix = [ + [ 'title', 'varchar', '256' ], + [ 'saved_prefs', 'text', '' ], + [ 'link_order', 'text', '' ], + [ 'description', 'text', '' ], + [ 'createdBy', 'int', '11' ], + [ 'createdAt', 'int', '11' ], + [ 'updatedAt', 'int', '11' ], + [ 'uuid', 'char', '36' ], + ]; + + /** + * The model constructor. + */ + public function __construct() { + parent::__construct(); + } + + public function create( $title, $saved_prefs, $link_order, $description = '' ) { + if ( ! Check::dataTitle( $title ) ) { + Debug::info( 'Dash: illegal title.' ); + return false; + } + $fields = [ + 'title' => $title, + 'description' => $description, + 'saved_prefs' => $saved_prefs, + 'link_order' => $link_order, + 'uuid' => generateUuidV4(), + 'createdBy' => App::$activeUser->ID, + 'createdAt' => time(), + ]; + if ( ! self::$db->insert( $this->tableName, $fields ) ) { + new CustomException( 'dashCreate' ); + Debug::error( "Dash: not created " . var_export($fields,true) ); + return false; + } + return self::$db->lastId(); + } + + public function update( $id, $title, $saved_prefs, $link_order, $description = '' ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Dash: illegal ID.' ); + return false; + } + if ( !Check::dataTitle( $title ) ) { + Debug::info( 'Dash: illegal title.' ); + return false; + } + $fields = [ + 'title' => $title, + 'description' => $description, + 'saved_prefs' => $saved_prefs, + 'link_order' => $link_order, + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + 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; + } + + public function byUser( $limit = null ) { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + if ( empty( $limit ) ) { + $views = self::$db->get( $this->tableName, $whereClause ); + } else { + $views = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$views->count() ) { + Debug::info( 'No Views found.' ); + return false; + } + return $this->filter( $views->results() ); + } + + public function getName( $id ) { + $views = self::findById( $id ); + if (false == $views) { + return 'unknown'; + } + return $views->title; + } + + public function simpleByUser() { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + $views = self::$db->get( $this->tableName, $whereClause ); + if ( !$views->count() ) { + Debug::warn( 'Could not find any Views' ); + return false; + } + + $views = $views->results(); + $out = []; + foreach ( $views as $view ) { + $out[ $view->title ] = $view->ID; + } + return $out; + } + + public function simpleObjectByUser() { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + $views = self::$db->get( $this->tableName, $whereClause ); + if ( !$views->count() ) { + Debug::warn( 'Could not find any Views' ); + return false; + } + + $views = $views->results(); + $out = []; + foreach ( $views as $view ) { + $obj = new \stdClass(); + $obj->title = $view->title; + $obj->ID = $view->ID; + $out[] = $obj; + } + return $out; + } + + public function findByUuid( $id ) { + $whereClause = ['uuid', '=', $id]; + + $dashboards = self::$db->get( $this->tableName, $whereClause ); + + if ( !$dashboards->count() ) { + Debug::info( 'No Dashboards found.' ); + return false; + } + return $this->filter( $dashboards->first() ); + } +} diff --git a/app/plugins/bookmarks/models/bookmarks.php b/app/plugins/bookmarks/models/bookmarks.php new file mode 100644 index 0000000..80b0277 --- /dev/null +++ b/app/plugins/bookmarks/models/bookmarks.php @@ -0,0 +1,800 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Houdini\Classes\Filters; +use TheTempusProject\Canary\Classes\CustomException; + +class Bookmarks extends DatabaseModel { + public $tableName = 'bookmarks'; + public $linkTypes = [ + 'Open in New Tab' => 'external', + 'Open in Same Tab' => 'internal', + ]; + + public $databaseMatrix = [ + [ 'title', 'varchar', '256' ], + [ 'url', 'text', '' ], + [ 'color', 'varchar', '48' ], + [ 'privacy', 'varchar', '48' ], + [ 'folderID', 'int', '11' ], + [ 'description', 'text', '' ], + [ 'createdBy', 'int', '11' ], + [ 'createdAt', 'int', '11' ], + [ 'meta', 'text', '' ], + [ 'icon', 'text', '' ], + [ 'archivedAt', 'int', '11' ], + [ 'refreshedAt', 'int', '11' ], + [ 'hiddenAt', 'int', '11' ], + [ 'order', 'int', '11' ], + [ 'linkType', 'varchar', '32' ], + [ 'uuid', 'char', '36' ], + ]; + + /** + * The model constructor. + */ + public function __construct() { + parent::__construct(); + } + + public function create( $title, $url, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $type = 'external', $user = null ) { + if ( empty( $user ) ) { + $user = App::$activeUser->ID; + } + $fields = [ + 'title' => $title, + 'url' => $url, + 'description' => $description, + 'color' => $color, + 'privacy' => $privacy, + 'createdBy' => $user, + 'uuid' => generateUuidV4(), + 'createdAt' => time(), + ]; + if ( !empty( $folderID ) ) { + $fields['folderID'] = $folderID; + } else { + $fields['folderID'] = null; + } + if ( ! self::$db->insert( $this->tableName, $fields ) ) { + new CustomException( 'bookmarkCreate' ); + Debug::error( "Bookmarks: not created " . var_export($fields,true) ); + return false; + } + return self::$db->lastId(); + } + + public function update( $id, $title, $url, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $type = 'external', $order = 0 ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'title' => $title, + 'url' => $url, + 'description' => $description, + 'color' => $color, + 'privacy' => $privacy, + // 'linkType' => $type, + // 'order' => $order, + ]; + if ( !empty( $folderID ) ) { + $fields['folderID'] = $folderID; + } + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function findByUuid( $id ) { + $whereClause = ['uuid', '=', $id]; + if ( empty( $limit ) ) { + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + } else { + $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$bookmarks->count() ) { + Debug::info( 'No Bookmarks found.' ); + return false; + } + return $this->filter( $bookmarks->first() ); + } + + public function byUser( $limit = null ) { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + if ( empty( $limit ) ) { + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + } else { + $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$bookmarks->count() ) { + Debug::info( 'No Bookmarks found.' ); + return false; + } + return $this->filter( $bookmarks->results() ); + } + + public function unsafeByFolder( $id, $limit = null ) { + $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] ); + } + if ( !$bookmarks->count() ) { + Debug::info( 'No Bookmarks found.' ); + return false; + } + return $this->filter( $bookmarks->results() ); + } + + 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 ( ! 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; + } + return $this->filter( $bookmarks->results() ); + } + + public function publicByFolder( $id, $limit = null ) { + $whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND']; + $whereClause = array_merge( $whereClause, [ 'folderID', '=', $id, 'AND', 'privacy', '=', 'public' ] ); + if ( empty( $limit ) ) { + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + } else { + $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$bookmarks->count() ) { + Debug::info( 'No Bookmarks found.' ); + return false; + } + return $this->filter( $bookmarks->results() ); + } + + public function noFolder( $id = 0, $limit = 10 ) { + $whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND']; + if ( !empty( $id ) ) { + $whereClause = array_merge( $whereClause, ['folderID', '!=', $id] ); + } else { + $whereClause = array_merge( $whereClause, [ 'folderID', 'IS', null] ); + } + if ( empty( $limit ) ) { + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + } else { + $bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [ 0, $limit ] ); + } + if ( !$bookmarks->count() ) { + Debug::info( 'No Bookmarks found.' ); + return false; + } + return $this->filter( $bookmarks->results() ); + } + + public function getName( $id ) { + $bookmarks = self::findById( $id ); + if (false == $bookmarks) { + return 'unknown'; + } + return $bookmarks->title; + } + + public function getColor( $id ) { + $bookmarks = self::findById( $id ); + if (false == $bookmarks) { + return 'default'; + } + return $bookmarks->color; + } + + public function simpleByUser() { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + if ( !$bookmarks->count() ) { + Debug::warn( 'Could not find any bookmarks' ); + return false; + } + + $bookmarks = $bookmarks->results(); + $out = []; + foreach ( $bookmarks as $bookmarks ) { + $out[ $bookmarks->title ] = $bookmarks->ID; + } + return $out; + } + + public function simpleObjectByUser() { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + if ( !$bookmarks->count() ) { + Debug::warn( 'Could not find any bookmarks' ); + return false; + } + + $bookmarks = $bookmarks->results(); + $out = []; + foreach ( $bookmarks as $bookmarks ) { + $obj = new \stdClass(); + $obj->title = $bookmarks->title; + $obj->ID = $bookmarks->ID; + $out[] = $obj; + } + return $out; + } + + public function deleteByFolder( $folderID ) { + $whereClause = [ 'createdBy', '=', App::$activeUser->ID, 'AND' ]; + $whereClause = array_merge( $whereClause, [ 'folderID', '=', $folderID ] ); + $bookmarks = self::$db->get( $this->tableName, $whereClause ); + if ( ! $bookmarks->count() ) { + Debug::info( 'No ' . $this->tableName . ' data found.' ); + return []; + } + foreach( $bookmarks->results() as $bookmark ) { + $this->delete( $bookmark->ID ); + } + return true; + } + + private function resolveShortenedUrl( $url ) { + $ch = curl_init($url); + + // Set curl options + curl_setopt($ch, CURLOPT_NOBODY, true); // We don't need the body + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the response + curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set a timeout + // curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // Maybe sketchy? + // Maybe sketchy? + // curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL host verification + // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL peer verification + // curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + + // added to support the regex site + // curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + + // = + // curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'AES256+EECDH:AES256+EDH' ); + + + + + // Execute curl + $response = curl_exec( $ch ); + + // Check if there was an error + if ( curl_errno( $ch ) ) { + + + // Get error details + $errorCode = curl_errno($ch); + $errorMessage = curl_error($ch); + // Log or display the error details + dv('cURL Error: ' . $errorMessage . ' (Error Code: ' . $errorCode . ')'); + + curl_close($ch); + // return $url; // Return the original URL if there was an error + + + $url = rtrim( $url, '/' ); + $ch2 = curl_init($url); + curl_setopt($ch2, CURLOPT_NOBODY, true); // We don't need the body + curl_setopt($ch2, CURLOPT_FOLLOWLOCATION, true); // Follow redirects + curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); // Return the response + curl_setopt($ch2, CURLOPT_TIMEOUT, 5); // Set a timeout + curl_exec($ch2); + + if ( curl_errno( $ch2 ) ) { + + } + curl_close( $ch ); + return $url; + } + + // Get the effective URL (the final destination after redirects) + $finalUrl = curl_getinfo( $ch, CURLINFO_EFFECTIVE_URL ); + curl_close( $ch ); + + return $finalUrl; + + // $headers = get_headers( $url, 1 ); + $headers = @get_headers($url, 1); + if ( $headers === false ) { + } + if ( isset( $headers['Location'] ) ) { + if (is_array($headers['Location'])) { + return end($headers['Location']); + } else { + return $headers['Location']; + } + } else { + return $url; + } + } + + public function filter( $data, $params = [] ) { + foreach ( $data as $instance ) { + if ( !is_object( $instance ) ) { + $instance = $data; + $end = true; + } + + $instance->iconHtml = ''; + if ( ! empty( $instance->icon ) ) { + if ( strpos($instance->icon, 'http') !== false) { + $instance->iconHtml = ''; + $out .= ''; + $out .= ''; + $out .= ''; + } else { + if ( ! empty( $instance->url ) ) { + $base_url = $this->getBaseUrl( $instance->url ); + $instance->iconHtml = '
'; + } + } + } + + if ( $instance->privacy == 'private' ) { + $instance->privacyBadge = 'Private'; + $instance->publish = ' + + + '; + } else { + $instance->privacyBadge = 'Public'; + $instance->publish = ' + + + '; + } + if ( empty( $instance->hiddenAt ) ) { + $instance->hidden_class = ''; + $instance->hideBtn = ' + + + '; + } else { + $instance->hidden_class = 'link-hidden'; + $instance->hideBtn = ' + + + '; + } + + if ( empty( $instance->archivedAt ) ) { + $instance->archived_class = ''; + $instance->archiveBtn = ' + + + '; + } else { + $instance->archived_class = 'link-archived'; + $instance->archiveBtn = ' + + + '; + } + + if ( ! empty( $instance->refreshedAt ) && time() < ( $instance->refreshedAt + ( 60 * 10 ) ) ) { + $instance->refreshBtn = ' + + + '; + } else { + $instance->refreshBtn = ' + + + '; + } + + $out[] = $instance; + if ( !empty( $end ) ) { + $out = $out[0]; + break; + } + } + return $out; + } + + public function hide( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'hiddenAt' => time(), + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function show( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'hiddenAt' => NULL, + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function publish( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'privacy' => 'public', + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function retract( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'privacy' => 'private', + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function archive( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'archivedAt' => time(), + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function unarchive( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $fields = [ + 'archivedAt' => NULL, + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + public function extractMetaTags($htmlContent) { + $doc = new \DOMDocument(); + @$doc->loadHTML($htmlContent); + $metaTags = []; + foreach ($doc->getElementsByTagName('meta') as $meta) { + $name = $meta->getAttribute('name') ?: $meta->getAttribute('property'); + $content = $meta->getAttribute('content'); + if ($name && $content) { + $metaTags[$name] = $content; + } + } + return $metaTags; + } + + public function fetchUrlData($url) { + $ch = curl_init(); + + // Set cURL options + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); // Include headers in the output + curl_setopt($ch, CURLOPT_NOBODY, false); // Include the body in the output + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects + curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Set a timeout + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable SSL host verification for testing + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL peer verification for testing + + // Execute cURL request + $response = curl_exec($ch); + + // Check if there was an error + if (curl_errno($ch)) { + $errorMessage = curl_error($ch); + curl_close($ch); + throw new \Exception('cURL Error: ' . $errorMessage); + } + + // Get HTTP status code + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + // Separate headers and body + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = substr($response, 0, $headerSize); + $body = substr($response, $headerSize); + + curl_close($ch); + + // Parse headers into an associative array + $headerLines = explode("\r\n", trim($headers)); + $headerArray = []; + foreach ($headerLines as $line) { + $parts = explode(': ', $line, 2); + if (count($parts) == 2) { + $headerArray[$parts[0]] = $parts[1]; + } + } + + return [ + 'http_code' => $httpCode, + 'headers' => $headerArray, + 'body' => $body, + ]; + } + + private function getMetaTagsAndFavicon( $url ) { + try { + // $url = 'https://runescape.wiki'; + $data = $this->fetchUrlData($url); + + // iv($data); + + // Get headers + $headers = $data['headers']; + iv($headers); + + // Get meta tags + $metaTags = $this->extractMetaTags($data['body']); + } catch (Exception $e) { + dv( 'Error: ' . $e->getMessage()); + + } + $metaInfo = [ + 'url' => $url, + 'title' => null, + 'description' => null, + 'image' => null, + 'favicon' => null + ]; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $html = curl_exec($ch); + curl_close($ch); + + if ($html === false) { + return null; + } + + $meta = @get_meta_tags( $url ); + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->strictErrorChecking = false; + $dom->loadHTML($html, LIBXML_NOERROR); + $xml = simplexml_import_dom($dom); + $arr = $xml->xpath('//link[@rel="shortcut icon"]'); + + // Get the title of the page + $titles = $dom->getElementsByTagName('title'); + if ($titles->length > 0) { + $metaInfo['title'] = $titles->item(0)->nodeValue; + } + + // Get the meta tags + $metaTags = $dom->getElementsByTagName('meta'); + $metadata = []; + foreach ($metaTags as $meta) { + $metadata[] = [ + 'name' => $meta->getAttribute('name'), + 'property' => $meta->getAttribute('property'), + 'content' => $meta->getAttribute('content') + ]; + if ($meta->getAttribute('name') === 'description') { + $metaInfo['description'] = $meta->getAttribute('content'); + } + if ($meta->getAttribute('itemprop') === 'image') { + $metaInfo['google_image'] = $meta->getAttribute('content'); + } + if ($meta->getAttribute('property') === 'og:image') { + $metaInfo['image'] = $meta->getAttribute('content'); + } + } + + // Get the link tags to find the favicon + $linkTags = $dom->getElementsByTagName('link'); + $metadata['links'] = []; + foreach ($linkTags as $link) { + $metadata['links'][] = [ $link->getAttribute('rel') => $link->getAttribute('href') ]; + if ( $link->getAttribute('rel') === 'icon' || $link->getAttribute('rel') === 'shortcut icon') { + $metaInfo['favicon'] = $link->getAttribute('href'); + break; + } + } + + $metaInfo['metadata'] = $metadata; + + return $metaInfo; + } + + public function retrieveInfo( $url ) { + $finalDestination = $this->resolveShortenedUrl( $url ); + $info = $this->getMetaTagsAndFavicon( $finalDestination ); + $base_url = $this->getBaseUrl( $finalDestination ); + + if ( ! empty( $info['favicon'] ) ) { + echo 'favicon exists' . PHP_EOL; + if ( stripos( $info['favicon'], 'http' ) !== false) { + echo 'favicon is full url' . PHP_EOL; + $imageUrl = $info['favicon']; + } else { + echo 'favicon is not full url' . PHP_EOL; + $imageUrl = trim( $base_url, '/' ) . '/' . ltrim( $info['favicon'], '/' ); + } + if ( $this->isValidImageUrl( $imageUrl ) ) { + echo 'image is valid' . PHP_EOL; + $info['favicon'] = $imageUrl; + } else { + echo 'image is not valid' . PHP_EOL; + $base_info = $this->getMetaTagsAndFavicon( $base_url ); + if ( ! empty( $base_info['favicon'] ) ) { + echo 'parent favicon exists!'; + if ( stripos( $base_info['favicon'], 'http' ) !== false) { + echo 'parent favicon is full url' . PHP_EOL; + $imageUrl = $base_info['favicon']; + } else { + echo 'parent favicon is not full url' . PHP_EOL; + $imageUrl = trim( $base_url, '/' ) . '/' . ltrim( $base_info['favicon'], '/' ); + } + if ( $this->isValidImageUrl( $imageUrl ) ) { + echo 'parent favicon image is valid' . PHP_EOL; + $info['favicon'] = $imageUrl; + } else { + echo 'parent favicon image is not valid' . PHP_EOL; + } + } + } + } else { + echo 'favicon does not exist' . PHP_EOL; + $base_info = $this->getMetaTagsAndFavicon( $base_url ); + if ( ! empty( $base_info['favicon'] ) ) { + echo 'parent favicon exists!' . PHP_EOL; + if ( stripos( $base_info['favicon'], 'http' ) !== false) { + echo 'parent favicon is full url' . PHP_EOL; + $imageUrl = $base_info['favicon']; + } else { + echo 'parent favicon is not full url' . PHP_EOL; + $imageUrl = trim( $base_url, '/' ) . '/' . ltrim( $base_info['favicon'], '/' ); + } + if ( $this->isValidImageUrl( $imageUrl ) ) { + echo 'parent favicon image is valid' . PHP_EOL; + $info['favicon'] = $imageUrl; + } else { + echo 'parent favicon image is not valid' . PHP_EOL; + } + } + } + + return $info; + } + + public function refreshInfo( $id ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Bookmarks: illegal ID.' ); + return false; + } + $bookmark = self::findById( $id ); + if ( $bookmark == false ) { + Debug::info( 'Bookmarks not found.' ); + return false; + } + if ( $bookmark->createdBy != App::$activeUser->ID ) { + Debug::info( 'You do not have permission to modify this bookmark.' ); + return false; + } + if ( time() < ( $bookmark->refreshedAt + ( 60 * 10 ) ) ) { + Debug::info( 'You may only fetch bookmarks once every 10 minutes.' ); + return false; + } + + $info = $this->retrieveInfo( $bookmark->url ); + + $fields = [ + // 'refreshedAt' => time(), + ]; + + if ( empty( $bookmark->title ) && ! empty( $info['title'] ) ) { + $fields['title'] = $info['title']; + } + if ( empty( $bookmark->description ) && ! empty( $info['description'] ) ) { + $fields['description'] = $info['description']; + } + + if ( ( empty( $bookmark->icon ) || ! $this->isValidImageUrl( $bookmark->icon ) ) && ! empty( $info['favicon'] ) ) { + $fields['icon'] = $info['favicon']; + } + $fields['meta'] = json_encode( $info['metadata'] ); + + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'bookmarkUpdate' ); + Debug::error( "Bookmarks: $id not updated" ); + return false; + } + return true; + } + + private function getBaseUrl ( $url ) { + if ( empty($url) ) { + return $url; + } + + $parsedUrl = parse_url($url); + if (isset($parsedUrl['scheme']) && isset($parsedUrl['host'])) { + return $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . '/'; + } else { + return null; // URL is not valid or cannot be parsed + } + } + + public function isValidImageUrl($url) { + $headers = @get_headers($url); + if ($headers && strpos($headers[0], '200') !== false) { + + return true; + // Further check to ensure it's an image + foreach ($headers as $header) { + if (strpos(strtolower($header), 'content-type:') !== false) { + if (strpos(strtolower($header), 'image/') !== false) { + return true; + } + } + } + } + return false; + } +} diff --git a/app/plugins/bookmarks/models/folders.php b/app/plugins/bookmarks/models/folders.php new file mode 100644 index 0000000..2655c36 --- /dev/null +++ b/app/plugins/bookmarks/models/folders.php @@ -0,0 +1,216 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Houdini\Classes\Filters; +use TheTempusProject\Canary\Classes\CustomException; + +class Folders extends DatabaseModel { + public $tableName = 'folders'; + public $databaseMatrix = [ + [ 'title', 'varchar', '256' ], + [ 'color', 'varchar', '48' ], + [ 'privacy', 'varchar', '48' ], + [ 'description', 'text', '' ], + [ 'folderID', 'int', '11' ], + [ 'createdBy', 'int', '11' ], + [ 'createdAt', 'int', '11' ], + [ 'uuid', 'char', '36' ], + ]; + + /** + * The model constructor. + */ + public function __construct() { + parent::__construct(); + } + + public function findByUuid( $id ) { + $whereClause = ['uuid', '=', $id]; + if ( empty( $limit ) ) { + $folders = self::$db->get( $this->tableName, $whereClause ); + } else { + $folders = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$folders->count() ) { + Debug::info( 'No Folders found.' ); + return false; + } + return $this->filter( $folders->first() ); + } + + public function create( $title, $folderID = 0, $description = '', $color = 'default', $privacy = 'private', $user = null ) { + if ( empty( $user ) ) { + $user = App::$activeUser->ID; + } + if ( ! Check::dataTitle( $title ) ) { + Debug::info( 'Folders: illegal title.' ); + return false; + } + $fields = [ + 'title' => $title, + 'description' => $description, + 'color' => $color, + 'privacy' => $privacy, + 'uuid' => generateUuidV4(), + 'createdBy' => $user, + 'createdAt' => time(), + ]; + + if ( !empty( $folderID ) ) { + $fields['folderID'] = $folderID; + } + if ( ! self::$db->insert( $this->tableName, $fields ) ) { + new CustomException( 'folderCreate' ); + Debug::error( "Folders: not created " . var_export($fields,true) ); + return false; + } + return self::$db->lastId(); + } + + public function update( $id, $title, $folderID = 0, $description = '', $color = 'default', $privacy = 'private' ) { + if ( !Check::id( $id ) ) { + Debug::info( 'Folders: illegal ID.' ); + return false; + } + if ( !Check::dataTitle( $title ) ) { + Debug::info( 'Folders: illegal title.' ); + return false; + } + $fields = [ + 'title' => $title, + 'description' => $description, + 'color' => $color, + 'privacy' => $privacy, + ]; + if ( !empty( $folderID ) ) { + $fields['folderID'] = $folderID; + } + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'folderUpdate' ); + Debug::error( "Folders: $id not updated: $fields" ); + return false; + } + return true; + } + + public function byUser( $limit = null ) { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + if ( empty( $limit ) ) { + $folders = self::$db->get( $this->tableName, $whereClause ); + } else { + $folders = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$folders->count() ) { + Debug::info( 'No Folders found.' ); + return false; + } + return $this->filter( $folders->results() ); + } + + public function bySpecificUser( $userID, $limit = null ) { + $whereClause = ['createdBy', '=', $userID]; + if ( empty( $limit ) ) { + $folders = self::$db->get( $this->tableName, $whereClause ); + } else { + $folders = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$folders->count() ) { + Debug::info( 'No Folders found.' ); + return false; + } + return $this->filter( $folders->results() ); + } + + public function getName( $id ) { + $folder = self::findById( $id ); + if (false == $folder) { + return 'unknown'; + } + return $folder->title; + } + + public function getColor( $id ) { + $folder = self::findById( $id ); + if (false == $folder) { + return 'default'; + } + return $folder->color; + } + + public function simpleByUser() { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + $folders = self::$db->get( $this->tableName, $whereClause ); + if ( !$folders->count() ) { + Debug::warn( 'Could not find any folders' ); + return false; + } + + $folders = $folders->results(); + $out = []; + $out[ 'No Folder' ] = 0; + foreach ( $folders as $folder ) { + $out[ $folder->title ] = $folder->ID; + } + return $out; + } + + public function simpleObjectByUser() { + $whereClause = ['createdBy', '=', App::$activeUser->ID]; + $folders = self::$db->get( $this->tableName, $whereClause ); + if ( !$folders->count() ) { + Debug::warn( 'Could not find any folders' ); + return false; + } + + $folders = $folders->results(); + $out = []; + foreach ( $folders as $folder ) { + $obj = new \stdClass(); + $obj->title = $folder->title; + $obj->ID = $folder->ID; + $out[] = $obj; + } + return $out; + } + + public function filter( $data, $params = [] ) { + foreach ( $data as $instance ) { + if ( !is_object( $instance ) ) { + $instance = $data; + $end = true; + } + // Real Work Starts Here + $instance->prettyPrivacy = ucfirst( $instance->privacy ); + $instance->prettyTitle = ucfirst( $instance->title ); + + if ( $instance->privacy == 'private' ) { + $instance->privacyBadge = 'Private'; + } else { + $instance->privacyBadge = 'Public'; + } + + // Real Work Ends Here + $out[] = $instance; + if ( !empty( $end ) ) { + $out = $out[0]; + break; + } + } + return $out; + } +} diff --git a/app/plugins/bookmarks/plugin.php b/app/plugins/bookmarks/plugin.php new file mode 100644 index 0000000..a8b9aa2 --- /dev/null +++ b/app/plugins/bookmarks/plugin.php @@ -0,0 +1,180 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Plugins; + +use TheTempusProject\Classes\Plugin; +use TheTempusProject\Models\Events; +use TheTempusProject\Models\Bookmarks as Bookmark; +use TheTempusProject\Models\Folders; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Houdini\Classes\Template; +use TheTempusProject\TheTempusProject as App; + +class Bookmarks extends Plugin { + public $pluginName = 'TP Bookmarks'; + public $configName = 'bookmarks'; + public $pluginAuthor = 'JoeyK'; + public $pluginWebsite = 'https://TheTempusProject.com'; + public $modelVersion = '1.0'; + public $pluginVersion = '3.0'; + public $pluginDescription = 'A simple plugin which adds a site wide bookmark system.'; + public $permissionMatrix = [ + 'useBookmarks' => [ + 'pretty' => 'Can use the bookmarks feature', + 'default' => false, + ], + 'createEvents' => [ + 'pretty' => 'Can add events to bookmarks', + 'default' => false, + ], + ]; + public $main_links = [ + [ + 'text' => 'Bookmarks', + 'url' => '{ROOT_URL}bookmarks/index', + 'filter' => 'loggedin', + ], + [ + 'text' => 'Tutorials', + 'url' => '{ROOT_URL}tutorials', + 'filter' => 'bkmtuts', + ], + [ + 'text' => 'Extensions', + 'url' => '{ROOT_URL}extensions/index', + ], + ]; + public $info_footer_links = [ + [ + 'text' => 'Tutorials', + 'url' => '{ROOT_URL}tutorials', + ], + ]; + public $configMatrix = [ + 'enabled' => [ + 'type' => 'radio', + 'pretty' => 'Enable Bookmarks.', + 'default' => true, + ], + ]; + public $preferenceMatrix = [ + 'editModeSwitch' => [ + 'pretty' => 'Bookmarks default setting for edit mode', + 'type' => 'checkbox', + 'default' => 'false', + ], + 'showArchivedSwitch' => [ + 'pretty' => 'Show archived bookmarks by default', + 'type' => 'checkbox', + 'default' => 'false', + ], + 'showHiddenSwitch' => [ + 'pretty' => 'Show hidden bookmarks by default', + 'type' => 'checkbox', + 'default' => 'false', + ], + 'archiveButtonSwitch' => [ + 'pretty' => 'Show the archive buttons by default', + 'type' => 'checkbox', + 'default' => 'false', + ], + 'visibilityButtonSwitch' => [ + 'pretty' => 'Show the visibility buttons by default', + 'type' => 'checkbox', + 'default' => 'false', + ], + 'privacyButtonSwitch' => [ + 'pretty' => 'Show the privacy buttons by default', + 'type' => 'checkbox', + 'default' => 'true', + ], + 'shareButtonSwitch' => [ + 'pretty' => 'Show the share buttons by default', + 'type' => 'checkbox', + 'default' => 'true', + ], + 'addButtonSwitch' => [ + 'pretty' => 'Show the add buttons by default', + 'type' => 'checkbox', + 'default' => 'true', + ], + 'bookmarkTutorials' => [ + 'pretty' => 'Show tutorials for the bookmark feature', + 'type' => 'checkbox', + 'default' => 'true', + ], + ]; + public $resourceMatrix = [ + 'routes' => [ + [ + 'original_url' => 'chrome', + 'redirect_type' => 'external', + 'nickname' => 'Chrome Extension', + 'forwarded_url' => 'https://chromewebstore.google.com/detail/allthebookmarks/hcofhopnjoodmakhhmgmoohgpdhfkgii?authuser=0&hl=en', + ], + [ + 'original_url' => 'brave', + 'redirect_type' => 'external', + 'nickname' => 'Brave Extension', + 'forwarded_url' => 'https://chromewebstore.google.com/detail/allthebookmarks/hcofhopnjoodmakhhmgmoohgpdhfkgii?authuser=0&hl=en', + ], + [ + 'original_url' => 'firefox', + 'redirect_type' => 'external', + 'nickname' => 'Firefox Extension', + 'forwarded_url' => 'https://addons.mozilla.org/en-US/firefox/addon/allthebookmarks/', + ], + // [ + // 'original_url' => 'edge', + // 'redirect_type' => 'external', + // 'nickname' => 'Edge Extension', + // 'forwarded_url' => 'https://www.facebook.com/thetempusproject', + // ], + // [ + // 'original_url' => 'opera', + // 'redirect_type' => 'external', + // 'nickname' => 'Opera Extension', + // 'forwarded_url' => 'https://www.facebook.com/thetempusproject', + // ], + ] + ]; + + public $bookmarks; + public $folders; + protected static $loaded = false; + + public function __construct( $load = false ) { + $this->bookmarks = new Bookmark; + $this->folders = new Folders; + + if ( ! self::$loaded && $load ) { + $tutpref = true; + if ( App::$isLoggedIn ) { + if ( empty( App::$activeUser->prefs['bookmarkTutorials'] ) ) { + $tutpref = false; + } + } + $this->filters[] = [ + 'name' => 'bkmtuts', + 'find' => '#{BKMTUTS}(.*?){/BKMTUTS}#is', + 'replace' => ( $tutpref ? '$1' : '' ), + 'enabled' => true, + ]; + } + + parent::__construct( $load ); + if ( ! self::$loaded && $load ) { + self::$loaded = true; + } + } +} diff --git a/app/plugins/bookmarks/views/bookmarks/create.html b/app/plugins/bookmarks/views/bookmarks/create.html new file mode 100644 index 0000000..59d4c98 --- /dev/null +++ b/app/plugins/bookmarks/views/bookmarks/create.html @@ -0,0 +1,56 @@ +
+\ No newline at end of file diff --git a/app/plugins/bookmarks/views/bookmarks/edit.html b/app/plugins/bookmarks/views/bookmarks/edit.html new file mode 100644 index 0000000..98db339 --- /dev/null +++ b/app/plugins/bookmarks/views/bookmarks/edit.html @@ -0,0 +1,56 @@ ++ ++
+ ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/bookmarks/list.html b/app/plugins/bookmarks/views/bookmarks/list.html new file mode 100644 index 0000000..fa55915 --- /dev/null +++ b/app/plugins/bookmarks/views/bookmarks/list.html @@ -0,0 +1,38 @@ ++ ++
+ ++ +
++ + + + {LOOP} +Bookmark ++ + + + + + {/LOOP} + {ALT} ++ + {title} + + ++ {privacy} + ++ + + + + {/ALT} + ++ No results to show. + ++ Create +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/bookmarks/listPage.html b/app/plugins/bookmarks/views/bookmarks/listPage.html new file mode 100644 index 0000000..45e81f0 --- /dev/null +++ b/app/plugins/bookmarks/views/bookmarks/listPage.html @@ -0,0 +1,13 @@ ++ {LOOP} + +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/bookmarks/unsorted.html b/app/plugins/bookmarks/views/bookmarks/unsorted.html new file mode 100644 index 0000000..952ce0b --- /dev/null +++ b/app/plugins/bookmarks/views/bookmarks/unsorted.html @@ -0,0 +1,54 @@ ++ {panel} ++ {/LOOP} + {ALT} +++ {/ALT} +no folders
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/bookmarks/view.html b/app/plugins/bookmarks/views/bookmarks/view.html new file mode 100644 index 0000000..b0f17b0 --- /dev/null +++ b/app/plugins/bookmarks/views/bookmarks/view.html @@ -0,0 +1,96 @@ ++ ++
+ {BKMTUTS} ++ When you add bookmarks without a folder, they will be considered "Unsorted". There is nothing wrong with these bookmarks, + but much of the application relies on folder for organizing and displaying bookmarks. You may find that these bookmarks do not regularly appear in other sections of the application. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
+ {/BKMTUTS} ++ +
++ + + + {LOOP} +Bookmark ++ + + + + + {/LOOP} + {ALT} ++ + {title} + + ++ {privacy} + ++ + + + + {/ALT} + ++ No results to show. + ++ 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 new file mode 100644 index 0000000..715727f --- /dev/null +++ b/app/plugins/bookmarks/views/components/bookmarkListPanel.html @@ -0,0 +1,56 @@ +{LOOP} ++++++ ++++ + +Bookmark
+++ + + ++ + +++++ +
++ +Title: +{title} ++ +URL: +{url} ++ +Type: +{linkType} ++ +Privacy: +{privacy} ++ +Color: +{color} ++ +Created: +{DTC}{createdAt}{/DTC} ++ +Archived: +{DTC}{archivedAt}{/DTC} ++ +Hidden: +{DTC}{hiddenAt}{/DTC} ++ +Last Refreshed: +{DTC}{refreshedAt}{/DTC} ++ +Description ++ +{description} ++ +Icon ++ +{iconHtml} ++ +{icon} ++ +Meta ++ + +{meta} +++{/LOOP} +{ALT} ++++ + + + ++++++ +-
+ {bookmarkListRows}
+
++{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/bookmarkListRows.html b/app/plugins/bookmarks/views/components/bookmarkListRows.html new file mode 100644 index 0000000..9060780 --- /dev/null +++ b/app/plugins/bookmarks/views/components/bookmarkListRows.html @@ -0,0 +1,35 @@ +{LOOP} +No folders found.
+- + {iconHtml} + {title} + + {hideBtn} + {archiveBtn} + {publish} + + + + + + + +
+{/LOOP} +{ALT} +- +
+{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/linkSelect.html b/app/plugins/bookmarks/views/components/linkSelect.html new file mode 100644 index 0000000..16b3a32 --- /dev/null +++ b/app/plugins/bookmarks/views/components/linkSelect.html @@ -0,0 +1,16 @@ +No Bookmarks
++ + {LOOP} +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/publicList.html b/app/plugins/bookmarks/views/components/publicList.html new file mode 100644 index 0000000..4dc8c85 --- /dev/null +++ b/app/plugins/bookmarks/views/components/publicList.html @@ -0,0 +1,27 @@ +{LOOP} ++ ++ {/LOOP} ++
++ + + {title} + ++++{/LOOP} +{ALT} ++++ ++++ +++{title}+++++-
+ {bookmarkListRows}
+
++{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/publicListRows.html b/app/plugins/bookmarks/views/components/publicListRows.html new file mode 100644 index 0000000..85005ff --- /dev/null +++ b/app/plugins/bookmarks/views/components/publicListRows.html @@ -0,0 +1,30 @@ +{LOOP} +no Folders
+- + {iconHtml} + {title} + + + + + + +
+{/LOOP} +{ALT} +- +
+{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/shareListPanel.html b/app/plugins/bookmarks/views/components/shareListPanel.html new file mode 100644 index 0000000..4794f6c --- /dev/null +++ b/app/plugins/bookmarks/views/components/shareListPanel.html @@ -0,0 +1,54 @@ +{LOOP} +No Bookmarks
+++{/LOOP} +{ALT} +++++++ + + ++ + {privacyBadge} + {title} + + + + ++++++ +-
+ {bookmarkListRows}
+
++{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/components/shareListRows.html b/app/plugins/bookmarks/views/components/shareListRows.html new file mode 100644 index 0000000..06d825d --- /dev/null +++ b/app/plugins/bookmarks/views/components/shareListRows.html @@ -0,0 +1,31 @@ +{LOOP} +no Folders
+- + {iconHtml} + {title}{privacyBadge} + + {publish} + + + + + +
+{/LOOP} +{ALT} +- +
+{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/dash.html b/app/plugins/bookmarks/views/dash.html new file mode 100644 index 0000000..308a7ee --- /dev/null +++ b/app/plugins/bookmarks/views/dash.html @@ -0,0 +1,25 @@ +No Bookmarks
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboardExplainer.html b/app/plugins/bookmarks/views/dashboardExplainer.html new file mode 100644 index 0000000..daf1592 --- /dev/null +++ b/app/plugins/bookmarks/views/dashboardExplainer.html @@ -0,0 +1,14 @@ ++ ++
+ {BKMTUTS} ++ Dashboards are one of our most powerful and useful features of AllTheBookmarks. With dashboards, you're able to create, edit, share, and use pages built dynamically from your bookmark folders. + When you create a dashboard, you select any number of your organized folders, select a few display options, and you gain access to a new page just for those folders. +
++ Sometimes, there just isn't enough room on the toolbar. Instead of having 6 folders you always use, have 1, short link called "Games" that opens 1 page. I use this to group small sections of + my bookmarks together like games. I have PalWorld, WoW, Satisfactory, and Diablo links in a "Games" dashboard. I can just quickly go to that dash, find the folder I need and go. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
+ {/BKMTUTS} + {VIEW_OPTIONS} +
++ {folderPanels} +++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/bookmarkRows.html b/app/plugins/bookmarks/views/dashboards/bookmarkRows.html new file mode 100644 index 0000000..5fe5243 --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/bookmarkRows.html @@ -0,0 +1,30 @@ +{LOOP} ++ ++
++ Dashboards are one of our most powerful and useful features of AllTheBookmarks. With dashboards, you're able to create, edit, share, and use pages built dynamically from your bookmark folders. + When you create a dashboard, you select any number of your organized folders, select a few display options, and you gain access to a new page just for those folders. +
++ Sometimes, there just isn't enough room on the toolbar. Instead of having 6 folders you always use, have 1, short link called "Games" that opens 1 page. I use this to group small sections of + my bookmarks together like games. I have PalWorld, WoW, Satisfactory, and Diablo links in a "Games" dashboard. I can just quickly go to that dash, find the folder I need and go. +
+- + {iconHtml} + {title} + + + + + + +
+{/LOOP} +{ALT} +- +
+{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/create.html b/app/plugins/bookmarks/views/dashboards/create.html new file mode 100644 index 0000000..8018a51 --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/create.html @@ -0,0 +1,43 @@ +No Bookmarks
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/dashOptions.html b/app/plugins/bookmarks/views/dashboards/dashOptions.html new file mode 100644 index 0000000..609473d --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/dashOptions.html @@ -0,0 +1,27 @@ ++ ++
+Dashboards are groups of folders that usually share a theme. When links are added to folders they will automatically be added to dashboards unless hidden or archived.
+ ++ +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/edit.html b/app/plugins/bookmarks/views/dashboards/edit.html new file mode 100644 index 0000000..ee6b7c6 --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/edit.html @@ -0,0 +1,43 @@ +++ + +Status:++ + +++ + ++++Buttons:++ + +++ + +++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/folderPanels.html b/app/plugins/bookmarks/views/dashboards/folderPanels.html new file mode 100644 index 0000000..101df2c --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/folderPanels.html @@ -0,0 +1,54 @@ +{LOOP} ++ ++
+Dashboards are groups of folders that usually share a theme. When links are added to folders they will automatically be added to dashboards unless hidden or archived.
+ +++{/LOOP} +{ALT} ++++ + + + ++++++ +-
+ {bookmarkRows}
+
++{/ALT} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/list.html b/app/plugins/bookmarks/views/dashboards/list.html new file mode 100644 index 0000000..9d8b3e3 --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/list.html @@ -0,0 +1,54 @@ +No folders found.
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/dashboards/view.html b/app/plugins/bookmarks/views/dashboards/view.html new file mode 100644 index 0000000..fc574d1 --- /dev/null +++ b/app/plugins/bookmarks/views/dashboards/view.html @@ -0,0 +1,29 @@ ++ ++
+ {BKMTUTS} ++ Dashboards are one of our most powerful and useful features of AllTheBookmarks. With dashboards, you're able to create, edit, share, and use pages built dynamically from your bookmark folders. + When you create a dashboard, you select any number of your organized folders, select a few display options, and you gain access to a new page just for those folders. +
++ Sometimes, there just isn't enough room on the toolbar. Instead of having 6 folders you always use, have 1, short link called "Games" that opens 1 page. I use this to group small sections of + my bookmarks together like games. I have PalWorld, WoW, Satisfactory, and Diablo links in a "Games" dashboard. I can just quickly go to that dash, find the folder I need and go. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
+ {/BKMTUTS} ++++ +
++ + + + {LOOP} +Title +Description ++ + + + + {/LOOP} + {ALT} +{title} +{description} ++ + + + + {/ALT} + ++ No results to show. + ++ Create +++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/export.html b/app/plugins/bookmarks/views/export.html new file mode 100644 index 0000000..fb9f18f --- /dev/null +++ b/app/plugins/bookmarks/views/export.html @@ -0,0 +1,84 @@ ++ ++
+ ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/exportExplainer.html b/app/plugins/bookmarks/views/exportExplainer.html new file mode 100644 index 0000000..fc332ba --- /dev/null +++ b/app/plugins/bookmarks/views/exportExplainer.html @@ -0,0 +1,17 @@ ++ ++
+ {BKMTUTS} ++ Our export tool allows you to select bookmarks and folders from your AllTheBookmarks account and generate a file that you can then import into any browser. + Giving you access to all your bookmarks right inside your browser. +
++ You can simply select the folders you want to bring into your browser on this page and click generate. A notification will pop-up showing you where you can + find your file for download. You take that newly saved file an import it into your browser of choice. +
++ If you need help importing your bookmarks, we have tutorials available for every major browser that can help. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
+ {/BKMTUTS} +Select which folders to include in the export
+ ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/extensions/bookmarklet.html b/app/plugins/bookmarks/views/extensions/bookmarklet.html new file mode 100644 index 0000000..fff29c9 --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/bookmarklet.html @@ -0,0 +1,108 @@ ++ ++
++ Our export tool allows you to select bookmarks and folders from your AllTheBookmarks account and generate a file that you can then import into any browser. + Giving you access to all your bookmarks right inside your browser. +
++ You can simply select the folders you want to bring into your browser on this page and click generate. A notification will pop-up showing you where you can + find your file for download. You take that newly saved file an import it into your browser of choice. +
++ If you need help importing your bookmarks, we have tutorials available for every major browser that can help. +
++ +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/extensions/bookmarkletExplainer.html b/app/plugins/bookmarks/views/extensions/bookmarkletExplainer.html new file mode 100644 index 0000000..a90774a --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/bookmarkletExplainer.html @@ -0,0 +1,19 @@ +
++++++ You can quickly and easily add bookmarks to your AllTheBookmarks account from any mobile device. + Since you can't use extensions on mobile, you can log in from your mobile device and visit this page. +
++ Below is a code snippet that you can copy. Copy the code, then create a new bookmark on your mobile device as you would normally. + Before saving the bookmark, change the name to something simple like "ATB Bookmarker" and paste this code as the url. +
++ Once you have the bookmarklet saved, you can simply go to your mobile bookmarks while on a page and this snippet will grab the info you need and add it to you account. +
++ ++ ++++ javascript:(function() { + const apiKey = "{BK_API_KEY}"; + const apiUrl = "{BK_API_URL}"; + const url = window.location.href; + const name = prompt("Enter a name for the bookmark:"); + {BK_JS_NOTES} + {BK_JS_COLOR} + {BK_JS_PRIVACY} + {BK_JS_FOLDER} + + if (!name) { + alert("Name is required."); + return; + } + + fetch(apiUrl + 'api/bookmarks/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}` + }, + body: JSON.stringify({ name, url, notes, color, privacy, folder }) + }) + .then(response => { + if (response.ok) { + alert("Bookmark saved successfully!"); + } else { + alert("Failed to save bookmark. Please check your API key."); + } + }) + .catch(error => { + console.error(error); + alert("An unknown error occurred while saving the bookmark."); + }); + })(); +
++ +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/extensions/brave.html b/app/plugins/bookmarks/views/extensions/brave.html new file mode 100644 index 0000000..18347c3 --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/brave.html @@ -0,0 +1,62 @@ +
++++++ You can quickly and easily add bookmarks to your AllTheBookmarks account from any mobile device. + Since you can't use extensions on mobile, you can log in from your mobile device and visit this page. +
++ Once logged in, there will be a code snippet that you can copy. Copy the code, then create a new bookmark on your mobile device as you would normally. + Before saving the bookmark, change the name to something simple like "ATB Bookmarker" and paste this code as the url. +
++ Once you have the bookmarklet saved, you can simply go to your mobile bookmarks while on a page and this snippet will grab the info you need and add it to you account. +
++ +diff --git a/app/plugins/bookmarks/views/extensions/chrome.html b/app/plugins/bookmarks/views/extensions/chrome.html new file mode 100644 index 0000000..0290a41 --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/chrome.html @@ -0,0 +1,62 @@ ++ ++ + +Brave Extension
+Your ultimate Brave addon to organize, store, and personalize your bookmarks effortlessly.
++ ++ + + +++ + ++++ ++Add & Manage Bookmarks
+Quickly add bookmarks, organize them, and never lose track of your favorite websites.
+++ + ++++ ++Create Folders
+Group your bookmarks into customizable folders for better organization.
+++ + ++++ ++Set Colors
+Personalize your folders and bookmarks with color coding.
++++++ ++Privacy Controls
+Keep your bookmarks private or share them—your choice, your control.
++ +diff --git a/app/plugins/bookmarks/views/extensions/edge.html b/app/plugins/bookmarks/views/extensions/edge.html new file mode 100644 index 0000000..29e9f70 --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/edge.html @@ -0,0 +1,62 @@ ++ ++ + +Chrome Extension
+Your ultimate Chrome addon to organize, store, and personalize your bookmarks effortlessly.
++ ++ + + +++ + ++++ ++Add & Manage Bookmarks
+Quickly add bookmarks, organize them, and never lose track of your favorite websites.
+++ + ++++ ++Create Folders
+Group your bookmarks into customizable folders for better organization.
+++ + ++++ ++Set Colors
+Personalize your folders and bookmarks with color coding.
++++++ ++Privacy Controls
+Keep your bookmarks private or share them—your choice, your control.
++ +diff --git a/app/plugins/bookmarks/views/extensions/firefox.html b/app/plugins/bookmarks/views/extensions/firefox.html new file mode 100644 index 0000000..df2e1cb --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/firefox.html @@ -0,0 +1,62 @@ ++ ++ + +Edge Extension
+Your ultimate Edge addon to organize, store, and personalize your bookmarks effortlessly.
++ ++ + + +++ + ++++ ++Add & Manage Bookmarks
+Quickly add bookmarks, organize them, and never lose track of your favorite websites.
+++ + ++++ ++Create Folders
+Group your bookmarks into customizable folders for better organization.
+++ + ++++ ++Set Colors
+Personalize your folders and bookmarks with color coding.
++++++ ++Privacy Controls
+Keep your bookmarks private or share them—your choice, your control.
++ +diff --git a/app/plugins/bookmarks/views/extensions/index.html b/app/plugins/bookmarks/views/extensions/index.html new file mode 100644 index 0000000..aba589e --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/index.html @@ -0,0 +1,78 @@ ++ ++ + +Firefox Extension
+Your ultimate Firefox addon to organize, store, and personalize your bookmarks effortlessly.
++ ++ + + +++ + ++++ ++Add & Manage Bookmarks
+Quickly add bookmarks, organize them, and never lose track of your favorite websites.
+++ + ++++ ++Create Folders
+Group your bookmarks into customizable folders for better organization.
+++ + ++++ ++Set Colors
+Personalize your folders and bookmarks with color coding.
++++++ ++Privacy Controls
+Keep your bookmarks private or share them—your choice, your control.
++ +diff --git a/app/plugins/bookmarks/views/extensions/opera.html b/app/plugins/bookmarks/views/extensions/opera.html new file mode 100644 index 0000000..4d058ec --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/opera.html @@ -0,0 +1,62 @@ +++ + +Our Browser Extensions
+Discover powerful tools to enhance your browsing experience, compatible with the browsers you love.
++ ++ + +++ + ++++ ++Chrome Extension
+Enhance your Chrome browser with advanced features and effortless organization.
+ Learn More +++ + ++++ ++Firefox Extension
+Seamlessly integrate with Firefox for a smoother browsing experience.
+ Learn More +++ + ++ ++++ + ++++ ++Brave Extension
+Enjoy secure and private browsing with Brave, enhanced by our extension.
+ Learn More ++++ +++++ + Unfortunately, our extensions are not currently supported on Safari. +
++ +diff --git a/app/plugins/bookmarks/views/extensions/safari.html b/app/plugins/bookmarks/views/extensions/safari.html new file mode 100644 index 0000000..b994aef --- /dev/null +++ b/app/plugins/bookmarks/views/extensions/safari.html @@ -0,0 +1,9 @@ ++ ++ + +Opera Extension
+Your ultimate Opera addon to organize, store, and personalize your bookmarks effortlessly.
++ ++ + + +++ + ++++ ++Add & Manage Bookmarks
+Quickly add bookmarks, organize them, and never lose track of your favorite websites.
+++ + ++++ ++Create Folders
+Group your bookmarks into customizable folders for better organization.
+++ + ++++ ++Set Colors
+Personalize your folders and bookmarks with color coding.
++++++ ++Privacy Controls
+Keep your bookmarks private or share them—your choice, your control.
++ +diff --git a/app/plugins/bookmarks/views/folders/create.html b/app/plugins/bookmarks/views/folders/create.html new file mode 100644 index 0000000..c785f56 --- /dev/null +++ b/app/plugins/bookmarks/views/folders/create.html @@ -0,0 +1,48 @@ + ++ ++AllTheBookmarks
+Safari Extension
+I'm just gonna level with ya here, I have no idea if this will work on safari, and I'm not going to buy an apple product to find out. If you would like to gift me a relatively modern mac computer, I will happily test and find out.
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/folders/edit.html b/app/plugins/bookmarks/views/folders/edit.html new file mode 100644 index 0000000..57671ad --- /dev/null +++ b/app/plugins/bookmarks/views/folders/edit.html @@ -0,0 +1,46 @@ ++ ++
+ + ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/folders/list.html b/app/plugins/bookmarks/views/folders/list.html new file mode 100644 index 0000000..019c76e --- /dev/null +++ b/app/plugins/bookmarks/views/folders/list.html @@ -0,0 +1,34 @@ ++ ++
+ ++ +
++ + + + {LOOP} +Title +Privacy +Description ++ + + {/LOOP} + {ALT} +{prettyTitle} +{prettyPrivacy} +{description} ++ + + + ++ + {/ALT} + ++ No results to show. + ++ Create +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/folders/listPage.html b/app/plugins/bookmarks/views/folders/listPage.html new file mode 100644 index 0000000..cc5dd91 --- /dev/null +++ b/app/plugins/bookmarks/views/folders/listPage.html @@ -0,0 +1,14 @@ ++ +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/folders/view.html b/app/plugins/bookmarks/views/folders/view.html new file mode 100644 index 0000000..22124b9 --- /dev/null +++ b/app/plugins/bookmarks/views/folders/view.html @@ -0,0 +1,61 @@ +
+ {BKMTUTS} ++ Just like the folders you have in your web browser, you can add, edit, or remove folders for organizing your bookmarks here. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
+ {/BKMTUTS} + {foldersList} ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/import.html b/app/plugins/bookmarks/views/import.html new file mode 100644 index 0000000..789e883 --- /dev/null +++ b/app/plugins/bookmarks/views/import.html @@ -0,0 +1,31 @@ ++++++ ++++ + +Bookmark Folder
+++ + + ++ + +++++ +
++ +Title: +{title} ++ +Privacy: +{privacy} ++ +Color: +{color} ++ +Created: +{DTC}{createdAt}{/DTC} ++ +Description ++ + +{description} ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/importExplainer.html b/app/plugins/bookmarks/views/importExplainer.html new file mode 100644 index 0000000..68c4160 --- /dev/null +++ b/app/plugins/bookmarks/views/importExplainer.html @@ -0,0 +1,16 @@ ++ ++
+ {BKMTUTS} ++ Our import tool allows you to upload a file from you browser (Chrome, Firefox, etc) that will add all of your bookmarks and folders to your allthebookmarks.com dashboard. +
++ The tool has been tested with all major browsers except safari. This includes Firefox, Chrome, Opera, Brave, and Edge. + You will need to browse your computer and select the .html file where you exported your bookmarks from your browser. +
++ If you need help exporting your bookmarks, we have tutorials available for every major browser that can help. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
+ {/BKMTUTS} + ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/indexExplainer.html b/app/plugins/bookmarks/views/indexExplainer.html new file mode 100644 index 0000000..a30b67f --- /dev/null +++ b/app/plugins/bookmarks/views/indexExplainer.html @@ -0,0 +1,19 @@ ++ ++
++ Our import tool allows you to upload a file from you browser (Chrome, Firefox, etc) that will add all of your bookmarks and folders to your allthebookmarks.com dashboard. +
++ The tool has been tested with all major browsers except safari. This includes Firefox, Chrome, Opera, Brave, and Edge. + You will need to browse your computer and select the .html file where you exported your bookmarks from your browser. +
++ If you need help exporting your bookmarks, we have tutorials available for every major browser that can help. +
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/introduction.html b/app/plugins/bookmarks/views/introduction.html new file mode 100644 index 0000000..09bfdaf --- /dev/null +++ b/app/plugins/bookmarks/views/introduction.html @@ -0,0 +1,58 @@ ++ ++
++ This is one of the main pages you'll be using to manage your bookmarks. Here you will find a list of all of your folders and the bookmarks they contain. + Hidden, archived, private, and public bookmarks can all be reviewed on this page using the filter options below. Some controls may be hidden by default, + so you're encouraged to toggle them to better understand your options for managing your bookmarks. +
++ Once you start collecting hundreds and thousands of bookmarks, this page can get a bit overwhelming. We encourage you to check out our Dashboards feature + for a more robust way to interact with all your saved bookmarks. Make use of the ability to hide and archive bookmarks to keep organized as well. +
++ Add Bookmarks + Add Folders +++ +\ No newline at end of file diff --git a/app/plugins/bookmarks/views/nav/folderTabs.html b/app/plugins/bookmarks/views/nav/folderTabs.html new file mode 100644 index 0000000..6303c91 --- /dev/null +++ b/app/plugins/bookmarks/views/nav/folderTabs.html @@ -0,0 +1,10 @@ + +{userFolderTabs} \ No newline at end of file diff --git a/app/plugins/bookmarks/views/nav/tutorialCrumbs.html b/app/plugins/bookmarks/views/nav/tutorialCrumbs.html new file mode 100644 index 0000000..e69de29 diff --git a/app/plugins/bookmarks/views/nav/userFolderTabs.html b/app/plugins/bookmarks/views/nav/userFolderTabs.html new file mode 100644 index 0000000..7ac7ccc --- /dev/null +++ b/app/plugins/bookmarks/views/nav/userFolderTabs.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/plugins/bookmarks/views/nav/viewOptions.html b/app/plugins/bookmarks/views/nav/viewOptions.html new file mode 100644 index 0000000..de5e889 --- /dev/null +++ b/app/plugins/bookmarks/views/nav/viewOptions.html @@ -0,0 +1,66 @@ +++Welcome to All The Bookmarks
+You've unlocked the ultimate tool for organizing, sharing, and managing all of your bookmarks effortlessly.
++
+ ++++ ++Organize Your Bookmarks
+Create folders to keep your bookmarks tidy and easy to find.
++ ++Share Collections
+Share dashboards with groups of folders to collaborate seamlessly.
++ ++Import & Export
+Sync your bookmarks across major browsers with our easy import/export tools.
+
+ +++++Bookmark Anything; Anytime, Anywhere
+Our browser extension works with Chrome, Firefox, Brave, Edge, and Opera, making it simple to save and manage bookmarks directly from your browser.
+ Install Extension ++++
+ ++++++
++Dynamic Dashboards
+Group folders into dashboards and share them to keep everyone updated on the latest changes.
+ Learn More About Dashboards +
+ +++Ready to Get Started?
+Start organizing, sharing, and syncing your bookmarks today!
+ Create Your First Folder ++diff --git a/app/plugins/bookmarks/views/public/bookmark.html b/app/plugins/bookmarks/views/public/bookmark.html new file mode 100644 index 0000000..aa8e238 --- /dev/null +++ b/app/plugins/bookmarks/views/public/bookmark.html @@ -0,0 +1,48 @@ ++++ + + +++ +++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/public/folder.html b/app/plugins/bookmarks/views/public/folder.html new file mode 100644 index 0000000..8008fd2 --- /dev/null +++ b/app/plugins/bookmarks/views/public/folder.html @@ -0,0 +1,49 @@ ++++++ ++++ + +Bookmark
++++ + +++++ +
++ +Title: +{title} ++ +URL: +{url} ++ +Created: +{DTC}{createdAt}{/DTC} ++ +Last Refreshed: +{DTC}{refreshedAt}{/DTC} ++ +Icon ++ + +{iconHtml} ++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/share.html b/app/plugins/bookmarks/views/share.html new file mode 100644 index 0000000..58dba19 --- /dev/null +++ b/app/plugins/bookmarks/views/share.html @@ -0,0 +1,30 @@ ++++++ ++++ + +Bookmark Folder
++++ + +++++ +
++ +Title: +{title} ++ +Color: +{color} ++ +Added on: +{DTC date}{createdAt}{/DTC} ++ +Description ++ + +{description} ++++ {panel} +++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/shareExplainer.html b/app/plugins/bookmarks/views/shareExplainer.html new file mode 100644 index 0000000..b742d53 --- /dev/null +++ b/app/plugins/bookmarks/views/shareExplainer.html @@ -0,0 +1,12 @@ ++ ++
+ {BKMTUTS} +++ {/BKMTUTS} ++ This is the share page, designed to very quickly show you what folders and urls you have set to public and have the ability to share with anyone. +
++ Any link or folder can be shared and by default, the extensions and app default to private. These "public" items can be viewed by anyone with the link provided from this page. +
++ This explanation and others like it can be hidden in your Profile Settings. +
+
++ {LOOP} + {panel} + {/LOOP} + {ALT} ++++ {/ALT} +No public folders found.
++\ No newline at end of file diff --git a/app/plugins/bookmarks/views/tutorials/brave/export.html b/app/plugins/bookmarks/views/tutorials/brave/export.html new file mode 100644 index 0000000..3536c8f --- /dev/null +++ b/app/plugins/bookmarks/views/tutorials/brave/export.html @@ -0,0 +1,43 @@ ++ ++
++ This is the share page, designed to very quickly show you what folders and urls you have set to public and have the ability to share with anyone. +
++ Any link or folder can be shared and by default, the extensions and app default to private. These "public" items can be viewed by anyone with the link provided from this page. +
++ \ No newline at end of file diff --git a/app/plugins/bookmarks/views/tutorials/brave/import.html b/app/plugins/bookmarks/views/tutorials/brave/import.html new file mode 100644 index 0000000..9994cf6 --- /dev/null +++ b/app/plugins/bookmarks/views/tutorials/brave/import.html @@ -0,0 +1,45 @@ +++++How to Export bookmarks from Brave
+ {tutorialCrumbs} +
++ Exporting your bookmarks is lightning fast and simple inside of brave. +
+Step 1: open the main application menu
++ Simply left-click these three lines on the right side of your main toolbar. +
++++
Step 2: Open the Bookmark Manager
++ In the list, hover over Bookmarks and Lists then select Bookmark Manager. +
++++
Step 2: Open the Export Menu
++ On this page, in the top right, you should see another set of three dots, click these. +
++++
+ From this list, select Export Bookmarks. +
+++ ++
+ Finally, save the .html file somewhere easy to access when you use the imports feature. +
++ \ No newline at end of file diff --git a/app/plugins/bookmarks/views/tutorials/brave/pin.html b/app/plugins/bookmarks/views/tutorials/brave/pin.html new file mode 100644 index 0000000..5af950d --- /dev/null +++ b/app/plugins/bookmarks/views/tutorials/brave/pin.html @@ -0,0 +1,36 @@ +++++How to Import bookmarks in Brave
+ {tutorialCrumbs} +
++ Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of brave. +
+Step 1: open the main application menu
++ Simply left-click these three lines on the right side of your main toolbar. +
++++
Step 2: Open the Bookmark Manager
++ In the list, hover over Bookmarks and Lists then select Bookmark Manager. +
++++
Step 2: Open the Import Menu
+ ++ On this page, in the top right, you should see another set of three dots, click these. +
+++ ++
+ From this list, select Import Bookmarks. +
+++ ++
+ Finally, select the .html file you saved when using the exports feature. +
++ \ No newline at end of file diff --git a/app/plugins/bookmarks/views/tutorials/brave/settings.html b/app/plugins/bookmarks/views/tutorials/brave/settings.html new file mode 100644 index 0000000..d017579 --- /dev/null +++ b/app/plugins/bookmarks/views/tutorials/brave/settings.html @@ -0,0 +1,34 @@ +++++How to pin the extension to your toolbar in Brave
+ {tutorialCrumbs} +
++ If you have not already installed the AllTheBookmarks Brave extension, you can find it in the + Chrome Web Store. +++ Once installed, you can add the extension to your brave toolbar with just a few easy steps. +
+Step 1: Find the extensions icon on your toolbar.
++ It should look like this: +
++++
Step 2: Left click this icon and a list of your currently installed extensions should show.
++ Next to AllTheBookmarks, on the right you should see a pin icon, click this. +
++++
+ Once pinned, you should see the AllTheBookmarks icon on the toolbar. +
++++
+ \ No newline at end of file diff --git a/app/plugins/bookmarks/views/tutorials/card.html b/app/plugins/bookmarks/views/tutorials/card.html new file mode 100644 index 0000000..9242450 --- /dev/null +++ b/app/plugins/bookmarks/views/tutorials/card.html @@ -0,0 +1,38 @@ +++++How to Access Extension Settings in Brave
+ {tutorialCrumbs} +
++ If you have not already installed the AllTheBookmarks Brave extension, you can find it in the + Chrome Web Store. +++ With Brave, accessing the extension settings couldn't be easier. +
+Step 1: Locate the Extension Icon
++ Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon. +
++++
Step 2: Open the Options
++ From the dropdown menu that appears, select Options. You should be greeted by a settings page very similar to the one below. +
++++
+ (If you need help pinning the extension to your toolbar for easy access, we have a separate + tutorial for that.) +
++ {pretty} Tutorials
++ +++ {tutorialCrumbs} +++++ ++{pretty}
+
+ +
Bookmarks
' . PHP_EOL; + $htmlDoc .= '' . PHP_EOL; + foreach ( Input::post('BF_') as $key => $id ) { + if ( $id == 'unsorted' ) { + continue; + } + + $folder = self::$folders->findById( $id ); + if ( $folder == false ) { + Session::flash( 'error', 'Folder not found.' ); + return Redirect::to( 'bookmarks/index' ); + } + + $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; + + $folder = UPLOAD_DIRECTORY . App::$activeUser->username; + if ( !file_exists( $folder ) ) { + mkdir( $folder, 0777, true ); + } + $file = $folder . DIRECTORY_SEPARATOR . 'export.html'; + + $result = file_put_contents( $file, $htmlDoc ); + + if ($result !== false) { + $filename = basename($file); + $downloadUrl = '/uploads/' . App::$activeUser->username . '/export.html'; + + Session::flash( 'success', 'Your Export is available for download here.' ); + Redirect::to( 'bookmarks/export' ); + } else { + Session::flash( 'error', 'There was an issue exporting your bookmarks, please try again.' ); + return Redirect::to( 'bookmarks/export' ); + } + } + + public function share() { + $panelArray = []; + $folders = self::$folders->byUser(); + if ( empty( $folders ) ) { + return Views::view( 'bookmarks.shareExplainer' ); + } + foreach ( $folders as $key => $folder ) { + $panel = new \stdClass(); + $folderObject = new \stdClass(); + if ( $folder->privacy == 'private' ) { + $links = self::$bookmarks->publicByFolder( $folder->ID ); + } else { + $links = self::$bookmarks->byFolder( $folder->ID ); + } + $folderObject->bookmarks = $links; + + $folderObject->ID = $folder->ID; + $folderObject->uuid = $folder->uuid; + $folderObject->title = $folder->title; + $folderObject->color = $folder->color; + $folderObject->privacyBadge = $folder->privacyBadge; + + $folderObject->bookmarkListRows = Views::simpleView( 'bookmarks.components.shareListRows', $folderObject->bookmarks ); + $panel->panel = Views::simpleView( 'bookmarks.components.shareListPanel', [$folderObject] ); + $panelArray[] = $panel; + } + return Views::view( 'bookmarks.share', $panelArray ); + } + + + /** + * Private + */ + private function exportFolder( $title, $editedAt, $createdAt, $links ) { + $htmlDoc = '
' . PHP_EOL; + if ( ! empty( $links ) ) { + foreach ( $links as $key => $link ) { + $htmlDoc .= $this->exportLink( $link->url, $link->icon, $link->createdAt, $link->title ); + } + } + $htmlDoc .= '
' . PHP_EOL; + return $htmlDoc; + } + + private function exportLink( $url, $icon, $createdAt, $title ) { + $htmlDoc = '
How to Export bookmarks from Chrome
+ {tutorialCrumbs} ++
+ Exporting your bookmarks is lightning fast and simple inside of chrome. +
+Step 1: open the main application menu
++ Simply left-click these three dots on the right side of your main toolbar. +
+
Step 2: Open the Bookmark Manager
++ In the list, hover over Bookmarks and Lists then select Bookmark Manager. +
+
Step 2: Open the Export Menu
++ On this page, in the top right, you should see another set of three dots, click these. +
+
+ From this list, select Export Bookmarks. +
+
+ Finally, save the .html file somewhere easy to access when you use the imports feature. +
+How to Import bookmarks in Chrome
+ {tutorialCrumbs} ++
+ Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of chrome. +
+Step 1: open the main application menu
++ Simply left-click these three dots on the right side of your main toolbar. +
+
Step 2: Open the Bookmark Manager
++ In the list, hover over Bookmarks and Lists then select Bookmark Manager. +
+
Step 2: Open the Import Menu
+ ++ On this page, in the top right, you should see another set of three dots, click these. +
+
+ From this list, select Import Bookmarks. +
+
+ Finally, select the .html file you saved when using the exports feature. +
+How to pin the extension to your toolbar in Chrome
+ {tutorialCrumbs} ++
+ Once installed, you can add the extension to your chrome toolbar with just a few easy steps. +
+Step 1: Find the extensions icon on your toolbar.
++ It should look like this: +
+
Step 2: Left click this icon and a list of your currently installed extensions should show.
++ Next to AllTheBookmarks, on the right you should see a pin icon, click this. +
+
+ Once pinned, you should see the AllTheBookmarks icon on the toolbar. +
+
How to Access Extension Settings in Chrome
+ {tutorialCrumbs} ++
+ With Chrome, accessing the extension settings couldn't be easier. +
+Step 1: Locate the Extension Icon
++ Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon. +
+
Step 2: Open the Options
++ From the dropdown menu that appears, select Options. You should be greeted by a settings page very similar to the one below. +
+
+ (If you need help pinning the extension to your toolbar for easy access, we have a separate + tutorial for that.) +
+How to Export bookmarks in Edge
+ {tutorialCrumbs} ++
+ Exporting your bookmarks is lightning fast and simple inside of Edge. +
+ +Step 1: open the main application menu
++ Simply left-click these three dots on the right side of your main toolbar. +
+
Step 2: Open the Bookmark Menu
++ In the list, select Favorites and another menu should appear. +
+
Step 3: Open the Favorites Options
++ In this menu, on the top right click the three dots for a final dropdown. +
+
Step 4: Export your bookmarks
++ From this list, select Export favorites and you should be taken to the import page. +
+
+ Finally, save the .html file somewhere easy to access when you use the imports feature. +
+How to Import bookmarks in Edge
+ {tutorialCrumbs} ++
+ Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of Edge. +
+ +Step 1: open the main application menu
++ Simply left-click these three dots on the right side of your main toolbar. +
+
Step 2: Open the Bookmark Menu
++ In the list, select Favorites and another menu should appear. +
+
Step 3: Open the Favorites Options
++ In this menu, on the top right click the three dots for a final dropdown. +
+
Step 4: Open the Import Page
++ From this list, select Import favorites and you should be taken to the import page. +
+
Step 5: Open the Importer
++ On this page, under the title Import from other browsers click the button labeled Choose what to import. +
+
Step 6: Begin Importing
++ Under Import from make sure to select Favorites or bookmarks HTML file. +
+
+ Finally, select the .html file you saved when using the exports feature. +
+How to pin the extension to your toolbar in Edge
+ {tutorialCrumbs} ++
+ Once installed, you can add the extension to your Edge toolbar with just a few easy steps. +
+Step 1: Find the extensions icon on your toolbar.
++ It should look like this: +
+
Step 2: Left click this icon and a list of your currently installed extensions should show.
++ Next to AllTheBookmarks, on the right you should see an eye icon, click this. +
+
+ Once pinned, you should see the AllTheBookmarks icon on the toolbar. +
+
How to Access Extension Settings in Edge
+ {tutorialCrumbs} ++
+ With Edge, accessing the extension settings couldn't be easier. +
+ +Step 1: Locate the Extension Icon
++ Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon. +
+
Step 2: Open the Options
++ From the dropdown menu that appears, select Extension Options. You should be greeted by a settings page very similar to the one below. +
+
+ (If you need help pinning the extension to your toolbar for easy access, we have a separate + tutorial for that.) +
+How to Export bookmarks in Firefox
+ {tutorialCrumbs} ++
+ Exporting your bookmarks is lightning fast and simple inside of Firefox. +
+ +Step 1: open the main application menu
++ Simply left-click these three lines on the right side of your main toolbar. +
+
Step 2: Open the Bookmark Menu
++ In the list, select Bookmarks and another menu should appear. +
+
Step 3: Open the Bookmark Manager
++ In the list, select Manage Bookmarks. +
+
Step 4: Open the Export Menu
++ On this page, near the top left, you should see a button that says Import and Backup. Click this button and another menu should appear. +
+
+ From this list, select Export Bookmarks. +
+
+ Finally, save the .html file somewhere easy to access when you use the imports feature. +
+How to Import bookmarks in Firefox
+ {tutorialCrumbs} ++
+ Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of firefox. +
+ +Step 1: open the main application menu
++ Simply left-click these three lines on the right side of your main toolbar. +
+
Step 2: Open the Bookmark Menu
++ In the list, select Bookmarks and another menu should appear. +
+
Step 3: Open the Bookmark Manager
++ In the list, select Manage Bookmarks. +
+
Step 3: Open the Import Menu
++ On this page, near the top left, you should see a button that says Import and Backup. Click this button and another menu should appear. +
+
+ From this list, select Import Bookmarks. +
+
+ Finally, select the .html file you saved when using the exports feature. +
+How to pin the extension to your toolbar in Firefox
+ {tutorialCrumbs} ++
+ Once installed, you can add the extension to your firefox toolbar with just a few easy steps. +
+Step 1: Find the extensions icon on your toolbar.
++ It should look like this: +
+
Step 2: Left click this icon and a list of your currently installed extensions should show.
++ Next to AllTheBookmarks, on the right you should see a settings icon, click this then select Pin to Toolbar +
+
+ Once pinned, you should see the AllTheBookmarks icon on the toolbar. +
+
How to Access Extension Settings in Firefox
+ {tutorialCrumbs} ++
+ With Firefox, accessing the extension settings is a breeze. +
+ +Step 1: Locate the Extension Icon
++ Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon. +
+
Step 2: Open the Add-on manage page
++ From the dropdown menu that appears, select Manage Extension. +
+
Step 3: Open the extension setting page
++ The extension management page should appear similar to this. In the top right, click these three dots and select Options. +
+
+ You should be greeted by a settings page very similar to the one below. +
+
+ (If you need help pinning the extension to your toolbar for easy access, we have a separate + tutorial for that.) +
+Mobile Tutorials
+Browser Tutorials
+Opera
++ +
Firefox
++ +
Chrome
++ +
Brave
++ +
Edge
++ +
Safari
++ +
+ The Main menu link to this page can be hidden in your Profile Settings. +
+ {/BKMTUTS} + {/LOGGEDIN} +How to add the app to your Android home-screen.
+ {tutorialCrumbs} ++
Step 1: Open https://allthebookmarks.com in Chrome.
++ from this page, tap the menu icon (three dots) in the top-right corner. +
+
Step 2: Select "Add to Home screen" and a popup will let you choose the name.
++ Next, tap "Add". +
+
Step 3: A popup will let you drag the icon around or you can simply select "Add to Home screen".
+
+ The shortcut will now appear on your home screen. +
+
How to add the app to your iPhone home-screen
+ {tutorialCrumbs} ++
Step 1: Open https://allthebookmarks.com in Safari.
+ (this is required; Chrome doesn’t support adding shortcuts on iOS) +Step 2: Tap the Share icon (square with an upward arrow).
++ Next, Scroll down and select "Add to Home Screen" then tap "Add". +
+
+ The shortcut will appear on your home screen. +
+
{pretty} Tutorials
+{pretty}
++ +
How to Export bookmarks in Opera
+ {tutorialCrumbs} ++
+ Exporting your bookmarks is lightning fast and simple inside of Opera. +
+ +Step 1: open the main application menu
++ Simply left-click these three dots in the bottom left of the browser window. +
+
Step 2: Open the Bookmark Menu
++ In the list, click the icon next to Bookmarks and another window should open. +
+
Step 4: Export your bookmarks
++ In the bottom right of the page, click Import / Export and select Export Bookmarks. +
+
+ Finally, save the .html file somewhere easy to access when you use the imports feature. +
+How to Import bookmarks in Opera
+ {tutorialCrumbs} ++
+ Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of Opera. +
+ +Step 1: open the main application menu
++ Simply left-click these three dots in the bottom left of the browser window. +
+
Step 2: Open the Bookmark Menu
++ In the list, click the icon next to Bookmarks and another window should open. +
+
Step 3: Open the Import Page
++ In the bottom right of the page, click Import / Export and select Import Bookmarks and you should be taken to the import page. +
+
Step 4: Begin Importing
++ In this menu, click the dropdown and make sure to select Bookmarks HTML file. +
+
+ Finally, select the .html file you saved when using the exports feature. +
+How to pin the extension to your toolbar in Opera
+ {tutorialCrumbs} ++
+ Once installed, you can add the extension to your Opera toolbar with just a few easy steps. +
+Step 1: Find the extensions icon on your toolbar.
++ It should look like this: +
+
Step 0: Reveal the extensions toolbar. ( This step may not be required for all users ).
++ If you find that you are having trouble locating the extensions icon, double check that the additional toolbar is expanded. +
+
Step 2: Left click this icon and a list of your currently installed extensions should show.
++ Next to AllTheBookmarks, on the right you should see an eye icon, click this. +
+
+ Once pinned, you should see the AllTheBookmarks icon on the toolbar. +
+
How to Access Extension Settings in Opera
+ {tutorialCrumbs} ++
+ With Opera, accessing the extension settings couldn't be easier. +
+ +Step 1: Locate the Extension Icon
++ Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon. +
+
Step 2: Open the Options
++ From the dropdown menu that appears, select Options. You should be greeted by a settings page very similar to the one below. +
+
+ (If you need help pinning the extension to your toolbar for easy access, we have a separate + tutorial for that.) +
+', $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', $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/admin/products.php b/app/plugins/members/controllers/admin/products.php new file mode 100644 index 0000000..b40e80b --- /dev/null +++ b/app/plugins/members/controllers/admin/products.php @@ -0,0 +1,97 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Admin; + +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Houdini\Classes\Navigation; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Classes\AdminController; +use TheTempusProject\Models\MembershipProducts; +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Classes\Forms; +use TheTempusProject\Houdini\Classes\Issues; +use TheTempusProject\Bedrock\Functions\Check; + +class Products extends AdminController { + public static $products; + + public function __construct() { + parent::__construct(); + self::$title = 'Admin - Membership Products'; + self::$products = new MembershipProducts; + $view = Navigation::activePageSelect( 'nav.admin', '/admin/products' ); + Components::set( 'ADMINNAV', $view ); + } + + public function index( $data = null ) { + Views::view( 'members.admin.products.list', self::$products->list() ); + } + + public function create( $data = null ) { + if ( !Input::exists( 'submit' ) ) { + return Views::view( 'members.admin.products.create' ); + } + if ( !Forms::check( 'newMembershipProduct' ) ) { + Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] ); + return $this->index(); + } + $result = self::$products->create( Input::post( 'name' ), Input::post( 'description' ), Input::post( 'monthly_price' ), Input::post( 'yearly_price' ) ); + if ( $result ) { + Issues::add( 'success', 'Your product has been created.' ); + return $this->index(); + } else { + Issues::add( 'error', [ 'There was an unknown error submitting your data.' => Check::userErrors() ] ); + return $this->index(); + } + } + + public function edit( $id = null ) { + if ( !Input::exists( 'submit' ) ) { + return Views::view( 'members.admin.products.edit', self::$products->findById( $id ) ); + } + if ( !Forms::check( 'editMembershipProduct' ) ) { + Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] ); + return $this->index(); + } + + if ( self::$products->updatePost( $id, Input::post( 'name' ), Input::post( 'description' ), Input::post( 'monthly_price' ), Input::post( 'yearly_price' ) ) === true ) { + Issues::add( 'success', 'Your product Updated.' ); + return $this->index(); + } + Issues::add( 'error', 'There was an error with your request.' ); + $this->index(); + } + + public function view( $id = null ) { + $data = self::$products->findById( $id ); + if ( $data !== false ) { + return Views::view( 'members.admin.products.view', $data ); + } + Issues::add( 'error', 'Product not found.' ); + $this->index(); + } + + public function delete( $data = null ) { + if ( $data == null ) { + if ( Input::exists( 'MP_' ) ) { + $data = Input::post( 'MP_' ); + } + } + if ( !self::$products->delete( (array) $data ) ) { + Issues::add( 'error', 'There was an error with your request.' ); + } else { + Issues::add( 'success', 'Post has been deleted' ); + } + $this->index(); + } +} diff --git a/app/plugins/members/controllers/admin/records.php b/app/plugins/members/controllers/admin/records.php new file mode 100644 index 0000000..8266ee8 --- /dev/null +++ b/app/plugins/members/controllers/admin/records.php @@ -0,0 +1,56 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Admin; + +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Houdini\Classes\Navigation; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Classes\AdminController; +use TheTempusProject\Models\Memberships as MemberModel; + +class Records extends AdminController { + public static $memberships; + + public function __construct() { + parent::__construct(); + self::$title = 'Admin - Memberships'; + self::$memberships = new MemberModel; + $view = Navigation::activePageSelect( 'nav.admin', '/admin/member' ); + Components::set( 'ADMINNAV', $view ); + } + + public function index( $data = null ) { + Views::view( 'members.admin.memberships.list', self::$memberships->list() ); + } + + public function create( $data = null ) { + } + + public function edit( $data = null ) { + } + + public function view( $id = null ) { + $data = self::$memberships->findById( $id ); + if ( $data !== false ) { + return Views::view( 'members.admin.memberships.view', $data ); + } + Issues::add( 'error', 'Membership not found.' ); + $this->index(); + } + + public function delete( $data = null ) { + } + + public function preview( $data = null ) { + } +} diff --git a/app/plugins/members/controllers/api/stripe.php b/app/plugins/members/controllers/api/stripe.php new file mode 100644 index 0000000..392e4c2 --- /dev/null +++ b/app/plugins/members/controllers/api/stripe.php @@ -0,0 +1,188 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers\Api; + +use Stripe\StripeClient; +use Stripe\Event; +use TheTempusProject\Models\User; +use TheTempusProject\Classes\ApiController; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Bedrock\Classes\Config; +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Models\MembershipCustomers; +use TheTempusProject\Models\Memberships; + +class Stripe extends ApiController { + public static $stripe; + public static $customers; + public static $memberships; + + public function __construct() { + parent::__construct( false ); + $api_key = Config::getValue( 'memberships/stripeSecret' ); + self::$stripe = new StripeClient($api_key); + } + + public function webhook() { + try { + $payload = @file_get_contents('php://input'); + $payload = json_decode( $payload, true ); + if ( ! is_array( $payload ) ) { + throw new \Exception("Error Processing Request", 1); + } + $event = null; + $event = Event::constructFrom( $payload ); + + $eventData = $event->data->object; + + // $event->type gives the event type obv + switch ($event->type) { + // case 'invoice.paid': + // Debug::error( 'processing: ' . $event->type ); + // "id": "in_1QSDcJGsigymNdIJo0Z1a20K", + // "customer": "cus_RKtOtR7X7CwPRU", + // "charge": "ch_3QSDcJGsigymNdIJ0Smb7Rmx", + // "subscription": "sub_1QSDcJGsigymNdIJWGw7Zrv9", + // break; + + // case 'charge.succeeded': + // Debug::error( 'processing: ' . $event->type ); + // "id": "ch_3QSDcJGsigymNdIJ0Smb7Rmx", + // "invoice": "in_1QSDcJGsigymNdIJo0Z1a20K", + // "status": "succeeded", + // break; + + case 'customer.subscription.updated': + case 'customer.subscription.paused': + case 'customer.subscription.resumed': + case 'customer.subscription.deleted': + Debug::error( 'processing: ' . $event->type ); + + self::$memberships = new Memberships; + $membership_id = self::$memberships->findBySubscriptionID( $eventData->id ); + if ( empty( $membership_id ) ) { + Debug::error( 'membership not found' ); + + self::$customers = new MembershipCustomers; + $customer = self::$customers->findByCustomerID( $eventData->customer ); + if ( empty( $customer ) ) { + Debug::error( 'customer not found' ); + Debug::v( $eventData->customer ); + break; + } + + $result = self::$memberships->create( + $eventData->customer, + $eventData->id, + $eventData->plan->id, + $eventData->current_period_start, + $eventData->current_period_end, + $eventData->status, + $customer->local_user, + 'frequency' + ); + } else { + $result = self::$memberships->update( $membership_id->ID, $eventData->current_period_start, $eventData->current_period_end, $eventData->status ); + } + + if ( empty( $result ) ) { + Debug::error( 'membership not updated' ); + Debug::v( $result ); + } + break; + case 'customer.subscription.created': + Debug::error( 'processing: ' . $event->type ); + + self::$memberships = new Memberships; + $membership_id = self::$memberships->findBySubscriptionID( $eventData->id ); + if ( ! empty( $membership_id ) ) { + Debug::error( 'subscription already created' ); + break; + } + self::$customers = new MembershipCustomers; + $customer = self::$customers->findByCustomerID( $eventData->customer ); + if ( empty( $customer ) ) { + Debug::error( 'customer not found' ); + Debug::v( $eventData->customer ); + break; + } + Debug::error( 'processing: ' . $event->type ); + + $result = self::$memberships->create( + $eventData->customer, + $eventData->id, + $eventData->plan->id, + $eventData->current_period_start, + $eventData->current_period_end, + $eventData->status, + $customer->local_user, + 'frequency' + ); + Debug::error( 'processing: ' . $event->type ); + + if ( empty( $result ) ) { + Debug::error( 'membership not made' ); + Debug::v( $result ); + } + Debug::error( 'done processing: ' . var_export($result,true) ); + break; + // case 'invoice.created': + // Debug::error( 'processing: ' . $event->type ); + // happens when the payment first starts + + // "id": "in_1QSDcJGsigymNdIJo0Z1a20K", + // "customer": "cus_RKtOtR7X7CwPRU", + // "status": "open", + // "total": 888, + // break; + // case 'checkout.session.completed': + // Debug::error( 'processing: ' . $event->type ); + // new customer has completed first checkout + // add thier record or update iit + + // "invoice": "in_1QSDcJGsigymNdIJo0Z1a20K", + // "mode": "subscription", + // "status": "complete", + // "customer": "cus_RKtOtR7X7CwPRU", + // break; + // case 'payment_intent.succeeded': + // Debug::error( 'processing: ' . $event->type ); + // $paymentIntent = $event->data->object; + // break; + // case 'payment_method.attached': + // Debug::error( 'processing: ' . $event->type ); + // $paymentMethod = $event->data->object; + // break; + default: + Debug::error( 'Skipped Event:' . $event->type ); + break; + } + + + $responseType = 'success'; + $response = true; + } catch(\UnexpectedValueException $e) { + Debug::error( 'UnexpectedValueException' ); + Debug::v( $e ); + http_response_code(400); + $responseType = 'error'; + $response = 'UnexpectedValueException'; + } catch(\Exception $e) { + Debug::error( 'Exception' ); + Debug::error( $e ); + http_response_code(400); + $responseType = 'error'; + $response = 'Exception'; + } + Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); + } +} \ No newline at end of file diff --git a/app/plugins/members/controllers/member.php b/app/plugins/members/controllers/member.php new file mode 100644 index 0000000..31959bf --- /dev/null +++ b/app/plugins/members/controllers/member.php @@ -0,0 +1,339 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Controllers; + +use TheTempusProject\Houdini\Classes\Template; +use TheTempusProject\Houdini\Classes\Views; +use TheTempusProject\Houdini\Classes\Issues; +use TheTempusProject\Classes\Controller; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Hermes\Functions\Redirect; +use TheTempusProject\Bedrock\Functions\Session; +use TheTempusProject\Models\MembershipCustomers; +use TheTempusProject\Models\MembershipProducts; +use TheTempusProject\Bedrock\Classes\Config; +use TheTempusProject\Bedrock\Functions\Input; +use Stripe\Checkout\Session as StripeSession; +use TheTempusProject\Hermes\Functions\Route as Routes; +use TheTempusProject\Houdini\Classes\Navigation; +use TheTempusProject\Models\Memberships; +use TheTempusProject\Houdini\Classes\Components; +use TheTempusProject\Classes\Forms; +use TheTempusProject\Bedrock\Functions\Hash; +use TheTempusProject\Canary\Bin\Canary as Debug; +use Stripe\StripeClient; + +class Member extends Controller { + public static $customers; + public static $products; + public static $stripe; + private static $loaded = false; + + public function __construct() { + parent::__construct(); + + if ( ! self::$loaded ) { + Template::noIndex(); + self::$customers = new MembershipCustomers; + self::$products = new MembershipProducts; + $api_key = Config::getValue( 'memberships/stripeSecret' ); + if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) { + Debug::error( "Memberships:__construct No Stripe Key found" ); + } else { + self::$stripe = new StripeClient( $api_key ); + } + self::$loaded = true; + } + } + + public function index() { + $this->confirmAuth(); + self::$title = 'Members Area'; + Views::view( 'members.members' ); + } + + public function managepayment() { + $this->confirmAuth(); + $customer = self::$customers->findByUserID( App::$activeUser->ID ); + if ( empty( $customer ) ) { + Session::flash( 'error', 'You do not have any active payment methods. You can subscribe by going here' ); + return Redirect::to( 'member/manage' ); + } + try { + $session = self::$stripe->billingPortal->sessions->create([ + 'customer' => $customer->stripe_customer, + 'return_url' => Routes::getAddress() . 'member/manage', + ]); + } catch (\Stripe\Exception\InvalidRequestException $e) { + Debug::error('Membership -> ManagePayment - Stripe not configured correctly'); + Debug::error( $e ); + Session::flash( 'error', 'There was an issue redirecting you to Stripe, please try again.' ); + return Redirect::to( 'member/manage' ); + } + + header('Location: ' . $session->url); + exit; + } + + public function cancelconfirm( $id = null ) { + $this->confirmAuth( $id ); + $memberships = new Memberships; + $result = $memberships->cancel( $id ); + if ( ! empty( $result ) ) { + Session::flash( 'success', 'Your Membership has been paused.' ); + Redirect::to( 'member/manage' ); + } else { + Session::flash( 'error', 'There was an error canceling your membership' ); + Redirect::to( 'member/manage' ); + } + } + + public function pauseconfirm( $id = null ) { + $this->confirmAuth( $id ); + $memberships = new Memberships; + $result = $memberships->cancel( $id ); + if ( ! empty( $result ) ) { + Session::flash( 'success', 'Your Membership has been paused.' ); + Redirect::to( 'member/manage' ); + } else { + Session::flash( 'error', 'There was an error canceling your membership' ); + Redirect::to( 'member/manage' ); + } + } + + public function pause( $id = null ) { + $this->confirmAuth( $id ); + self::$title = 'pause Membership'; + Components::set( 'pauseid', $id ); + Views::view( 'members.pause' ); + } + + public function resume( $id = null ) { + $this->confirmAuth( $id ); + self::$title = 'resume Membership'; + Views::view( 'members.resume' ); + } + + public function cancel( $id = null ) { + $this->confirmAuth( $id ); + self::$title = 'Cancel Membership'; + Components::set( 'cancelid', $id ); + Views::view( 'members.cancel' ); + } + + public function manage() { + if ( ! App::$isLoggedIn ) { + Session::flash( 'error', 'You do not have permission to access this page.' ); + return Redirect::home(); + } + self::$title = 'Manage Membership'; + + $menu = Views::simpleView( 'nav.usercp', App::$userCPlinks ); + Navigation::activePageSelect( $menu, null, true, true ); + + $memberships = new Memberships; + $userMemberships = $memberships->getUserSubs(); + Views::view( 'members.manage', $userMemberships ); + } + + public function upgrade() { + if ( ! App::$isLoggedIn ) { + Session::flash( 'error', 'You do not have permission to access this page.' ); + return Redirect::home(); + } + // need to check if the plan CAN be upgraded + self::$title = 'Upgrade Membership'; + Views::view( 'members.upgrade' ); + } + + public function join( $plan = 'monthly' ) { + if ( ! App::$isLoggedIn ) { + Session::flash( 'error', 'You do not have permission to access this page.' ); + return Redirect::home(); + } + $plan = strtolower( $plan ); + if ( ! in_array( $plan, ['monthly','yearly'] ) ) { + Session::flash( 'error', 'Unknown plan' ); + return Redirect::to( 'home/index' ); + } + + self::$title = 'Join {SITENAME}!'; + $stripePrice = $this->findPrice( $plan ); + + $product = self::$products->findByPriceID( $stripePrice ); + if ( empty( $product ) ) { + Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' ); + return Redirect::home(); + } + Views::view( 'members.join', $product ); + } + + public function checkout( $plan = 'monthly' ) { + if ( ! App::$isLoggedIn ) { + Session::flash( 'error', 'You do not have permission to access this page.' ); + return Redirect::home(); + } + $customer = self::$customers->findOrCreate( App::$activeUser->ID ); + if ( empty( $customer ) ) { + 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([ + 'payment_method_types' => ['card'], + 'customer' => $customer, + 'line_items' => [[ + 'price' => $stripePrice, + 'quantity' => 1, + ]], + 'mode' => 'subscription', + 'allow_promotion_codes' => true, + 'success_url' => $successUrl, + 'cancel_url' => Routes::getAddress() . 'member/payment/cancel', + ]); + header('Location: ' . $session->url); + exit; + } + + public function payment( $type = '' ) { + $type = strtolower( $type ); + if ( ! in_array( $type, ['cancel','complete','upgrade'] ) ) { + Session::flash( 'error', 'Unknown Payment' ); + return Redirect::to( 'home/index' ); + } + + if ( $type == 'cancel' ) { + self::$title = '(almost) Members Area'; + 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' ); + } + + // This combines a registration with a checkout + public function signup( $plan = 'monthly' ) { + $plan = strtolower( $plan ); + if ( ! in_array( $plan, ['monthly','yearly'] ) ) { + Session::flash( 'error', 'Unknown plan' ); + return Redirect::to( 'home/index' ); + } + + $product = self::$products->mainProduct(); + if ( empty( $product ) ) { + Session::flash( 'error', 'Unknown product' ); + return Redirect::to( 'home/index' ); + } + + $stripePrice = $this->findPrice( $plan ); + + $pretty = 'prettyPrice' . ucfirst( $plan ); + $prettyPrice = $product->$pretty; + + self::$title = 'Sign up for {SITENAME} ' . ucfirst( $plan ); + + Components::set( 'planName', ucfirst( $plan ) ); + Components::set( 'prettyPrice', $prettyPrice ); + Components::set( 'TERMS', Views::simpleView( 'terms' ) ); + + if ( App::$isLoggedIn ) { + Session::flash( 'notice', 'You are already logged in, you can subscribe here, or upgrade here.' ); + return Redirect::to( 'home/index' ); + } + + if ( !Input::exists() ) { + return Views::view( 'members.register' ); + } + + if ( ! Forms::check( 'register' ) ) { + Issues::add( 'error', [ 'There was an error with your registration.' => Check::userErrors() ] ); + return Views::view( 'members.register' ); + } + + self::$user->create( [ + 'username' => Input::post( 'username' ), + 'password' => Hash::make( Input::post( 'password' ) ), + 'email' => Input::post( 'email' ), + 'terms' => 1, + ] ); + + if ( !self::$user->logIn( Input::post( 'username' ), Input::post( 'password' ), Input::post( 'remember' ) ) ) { + Session::flash( 'error', 'Thank you for registering! Unfortunately, there was an issue logging you in, please log in and order again.' ); + return Redirect::to( 'home/index' ); + } + + $user = self::$user->authorize( Input::post( 'username' ), Input::post( 'password' ) ); + + $customer = self::$customers->findOrCreate( $user->ID ); + if ( empty( $customer ) ) { + Session::flash( 'error', 'Thank you for registering! Unfortunately, there was an issue communicating with Stripe and we can\'t collect payment right now.' ); + return Redirect::to( 'home/index' ); + } + $session = self::$stripe->checkout->sessions->create([ + 'payment_method_types' => ['card'], + 'customer' => $customer, + 'line_items' => [[ + 'price' => $stripePrice, + 'quantity' => 1, + ]], + 'mode' => 'subscription', + 'allow_promotion_codes' => true, + 'success_url' => Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}', + 'cancel_url' => Routes::getAddress() . 'member/payment/cancel', + ]); + header('Location: ' . $session->url); + exit; + } + + private function findPrice( $plan ) { + $plan = strtolower( $plan ); + if ( ! in_array( $plan, ['monthly','yearly'] ) ) { + Session::flash( 'error', 'Unknown plan' ); + return Redirect::to( 'home/index' ); + } + + $product = self::$products->mainProduct(); + if ( empty( $product ) ) { + Session::flash( 'error', 'Unknown product' ); + return Redirect::to( 'home/index' ); + } + $index = 'stripe_price_' . $plan; + $stripePrice = $product->$index; + return $stripePrice; + } + + private function confirmAuth() { + if ( ! App::$isLoggedIn || ! App::$isMember ) { + Session::flash( 'error', 'You do not have permission to access this page.' ); + return Redirect::home(); + } + } +} \ No newline at end of file diff --git a/app/plugins/members/forms.php b/app/plugins/members/forms.php new file mode 100644 index 0000000..295ea2c --- /dev/null +++ b/app/plugins/members/forms.php @@ -0,0 +1,47 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Plugins\Subscribe; + +use TheTempusProject\Bedrock\Functions\Input; +use TheTempusProject\Classes\Forms; + +class MembershipForms extends Forms { + /** + * Adds these functions to the form list. + */ + public function __construct() { + self::addHandler( 'newMembershipProduct', __CLASS__, 'newMembershipProduct' ); + self::addHandler( 'editMembershipProduct', __CLASS__, 'editMembershipProduct' ); + } + + /** + * Validates the subscribe form. + * + * @return {bool} + */ + public static function newMembershipProduct() { + // if ( !self::token() ) { + // return false; + // } + return true; + } + public static function editMembershipProduct() { + // if ( !self::token() ) { + // return false; + // } + return true; + } + +} + +new MembershipForms; diff --git a/app/plugins/members/models/membership_customers.php b/app/plugins/members/models/membership_customers.php new file mode 100644 index 0000000..fe1fc7b --- /dev/null +++ b/app/plugins/members/models/membership_customers.php @@ -0,0 +1,104 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Bedrock\Functions\Sanitize; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Bedrock\Classes\Config; +use TheTempusProject\Hermes\Functions\Route as Routes; + +class MembershipCustomers extends DatabaseModel { + public static $stripe; + public $tableName = 'membership_customers'; + + public $databaseMatrix = [ + [ 'stripe_customer', 'varchar', '155' ], + [ 'local_user', 'varchar', '155' ], + // renews? + // does this renew periodically? + // renewal period? + // if periodic, how frequent? + // renewal type? + // automatic, manual review, paid + ]; + + public function __construct() { + parent::__construct(); + } + + public function findByUserID( $d ) { + $data = self::$db->get( $this->tableName, [ 'local_user', '=', $d ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function findByCustomerID( $d ) { + $data = self::$db->get( $this->tableName, [ 'stripe_customer', '=', $d ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function create( $user_id ) { + $data = self::$db->get( 'users', ['ID', '=', $user_id] ); + if ( !$data->count() ) { + Debug::warn( "customer cannot be created, user not found" ); + return false; + } + $user = $data->first(); + $user_email = $user->email; + $user_name = $user->name; + + $api_key = Config::getValue( 'memberships/stripeSecret' ); + if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) { + Debug::error( "MembershipCustomers:create No Stripe Key found" ); + return false; + } + self::$stripe = new \Stripe\StripeClient( $api_key ); + + $customer = self::$stripe->customers->create([ + 'name' => $user_name, + 'email' => $user_email, + ]); + + $fields = [ + 'stripe_customer' => $customer->id, + 'local_user' => $user_id, + ]; + + if ( !self::$db->insert( $this->tableName, $fields ) ) { + Debug::error( "Membership Customer: $data not added: $fields" ); + new customException( 'membershipCustomerCreate' ); + return false; + } + + return $customer; + } + + public function findOrCreate( $user_id ) { + $user = $this->findByUserID( $user_id ); + if ( ! empty( $user ) ) { + return $user->stripe_customer; + } + $user = $this->create( $user_id ); + if ( ! empty( $user ) ) { + return $user->id; + } + } +} \ No newline at end of file diff --git a/app/plugins/members/models/membership_invoices.php b/app/plugins/members/models/membership_invoices.php new file mode 100644 index 0000000..9412be8 --- /dev/null +++ b/app/plugins/members/models/membership_invoices.php @@ -0,0 +1,38 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Bedrock\Functions\Sanitize; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; + +class MembershipInvoices extends DatabaseModel { + public static $stripe; + public $tableName = 'membership_invoices'; + + public $databaseMatrix = [ + [ 'name', 'varchar', '155' ], + // renews? + // does this renew periodically? + // renewal period? + // if periodic, how frequent? + // renewal type? + // automatic, manual review, paid + ]; + + public function __construct() { + parent::__construct(); + } +} \ No newline at end of file diff --git a/app/plugins/members/models/membership_products.php b/app/plugins/members/models/membership_products.php new file mode 100644 index 0000000..a4ab727 --- /dev/null +++ b/app/plugins/members/models/membership_products.php @@ -0,0 +1,222 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ + +namespace TheTempusProject\Models; + +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Bedrock\Functions\Sanitize; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Bedrock\Classes\Config; + +class MembershipProducts extends DatabaseModel { + public static $stripe; + private static $loaded = false; + public $tableName = 'membership_products'; + public $databaseMatrix = [ + [ 'name', 'varchar', '128' ], + [ 'description', 'text', '' ], + [ '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 + ]; + + public function __construct() { + parent::__construct(); + if ( ! self::$loaded ) { + $api_key = Config::getValue( 'memberships/stripeSecret' ); + if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) { + Debug::error( "MembershipProducts:__construct No Stripe Key found" ); + } else { + self::$stripe = new \Stripe\StripeClient( $api_key ); + } + self::$loaded = true; + } + } + + public function create( $name, $description, $monthly_price, $yearly_price ) { + if ( empty( self::$stripe ) ) { + return false; + } + $stripe_product = $this->createStripeProduct( $name, $description ); + $stripe_prices = $this->createStripePrices( $stripe_product->id, $monthly_price, $yearly_price ); + + $fields = [ + 'name' => $name, + 'description' => $description, + 'monthly_price' => $monthly_price, + 'yearly_price' => $yearly_price, + 'stripe_product' => $stripe_product->id, + 'stripe_price_monthly' => $stripe_prices['monthly']->id, + 'stripe_price_yearly' => $stripe_prices['yearly']->id, + ]; + + if ( !self::$db->insert( $this->tableName, $fields ) ) { + Debug::error( "Membership Product: $data not updated: $fields" ); + new customException( 'membershipProductCreate' ); + return false; + } + + return true; + } + + public function createStripeProduct( $name, $description ) { + if ( empty( self::$stripe ) ) { + return false; + } + $product = self::$stripe->products->create([ + 'name' => $name, + 'description' => $description, + ]); + return $product; + } + + public function createStripePrices( $product, $monthly_price, $yearly_price ) { + $out = []; + $out['monthly'] = $this->createStripeMonthlyPrice( $product, $monthly_price ); + $out['yearly'] = $this->createStripeYearlyPrice( $product, $yearly_price ); + return $out; + } + + public function createStripeMonthlyPrice( $product, $monthly_price ) { + if ( empty( self::$stripe ) ) { + return false; + } + return self::$stripe->prices->create([ + 'currency' => 'usd', + 'unit_amount' => $monthly_price, + 'recurring' => ['interval' => 'month'], + 'product' => $product, + 'lookup_key' => 'membership-monthly', + ]); + } + + public function createStripeYearlyPrice( $product, $yearly_price ) { + if ( empty( self::$stripe ) ) { + return false; + } + return self::$stripe->prices->create([ + 'currency' => 'usd', + 'unit_amount' => $yearly_price, + 'recurring' => ['interval' => 'year'], + 'product' => $product, + 'lookup_key' => 'membership-yearly', + ]); + } + + public function updateProduct( $id, $name, $description, $monthly_price, $yearly_price ) { + $product = $this->findById( $id ); + if ( $product === false ) { + return false; + } + if ( $product->monthly_price != $monthly_price ) { + $new_monthly = $this->updateStripeMonthlyPrice( $product->stripe_price_monthly, $monthly_price ); + } + if ( $product->yearly_price != $yearly_price ) { + $new_yearly = $this->updateStripeYearlyPrice( $product->stripe_price_yearly, $yearly_price ); + } + if ( ( $product->name != $name ) || ( $product->description != $description ) ) { + $this->updateStripeProduct( $product->stripe_product, $name, $description ); + } + + $fields = [ + 'name' => $name, + 'description' => $description, + 'monthly_price' => $monthly_price, + 'yearly_price' => $yearly_price, + 'stripe_price_monthly' => $new_monthly->id, + 'stripe_price_yearly' => $new_yearly->id, + ]; + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + new CustomException( 'membershipProductUpdate' ); + Debug::error( "membership Product: $id not updated: $fields" ); + + return false; + } + self::$log->admin( "Updated membership Product: $id" ); + return true; + } + + public function updateStripeProduct( $id, $name, $description ) { + if ( empty( self::$stripe ) ) { + return false; + } + $product = self::$stripe->products->update( + $id, + [ + 'name' => $name, + 'description' => $description, + ] + ); + return $product; + } + + public function updateStripeYearlyPrice( $product, $yearly_price ) { + $stripe->prices->update( + $yearly_price, + ['lookup_key' => ""] + ); + return $this->createStripeYearlyPrice( $product, $yearly_price ); + } + + public function updateStripeMonthlyPrice( $product, $monthly_price ) { + $stripe->prices->update( + $monthly_price, + ['lookup_key' => ""] + ); + return $this->createStripeMonthlyPrice( $product, $monthly_price ); + } + + public function filter( $postArray, $params = [] ) { + foreach ( $postArray as $instance ) { + if ( !is_object( $instance ) ) { + $instance = $postArray; + $end = true; + } + + $instance->prettyPriceMonthly = '$' . number_format( $instance->monthly_price / 100, 2 ); // Outputs: $99.49 + $instance->prettyPriceYearly = '$' . number_format( $instance->yearly_price / 100, 2 ); // Outputs: $99.49 + $instance->prettySavings = '$' . number_format( + ( $instance->yearly_price - ( $instance->monthly_price * 12 ) ) / 100, + 2 + ); // Outputs: $99.49 + + $out[] = $instance; + if ( !empty( $end ) ) { + $out = $out[0]; + break; + } + } + return $out; + } + + public function findByPriceID( $d ) { + $data = self::$db->get( $this->tableName, [ 'stripe_price_monthly', '=', $d, 'OR', 'stripe_price_yearly', '=', $d ] ); + if ( ! $data->count() ) { + return false; + } + return $this->filter( $data->first() ); + } + + public function mainProduct() { + $data = self::$db->get( $this->tableName, '*' ); + if ( ! $data->count() ) { + return false; + } + + return $this->filter( $data->first() ); + } +} \ No newline at end of file diff --git a/app/plugins/members/models/memberships.php b/app/plugins/members/models/memberships.php new file mode 100644 index 0000000..43e5916 --- /dev/null +++ b/app/plugins/members/models/memberships.php @@ -0,0 +1,174 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Models; + +use TheTempusProject\Canary\Bin\Canary as Debug; +use TheTempusProject\Bedrock\Functions\Check; +use TheTempusProject\Bedrock\Functions\Sanitize; +use TheTempusProject\Classes\DatabaseModel; +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Models\MembershipProducts; +use TheTempusProject\Bedrock\Classes\Config; +use TheTempusProject\Canary\Classes\CustomException; + +class Memberships extends DatabaseModel { + public static $stripe; + public static $products; + private static $loaded = false; + public $tableName = 'membership_records'; + public $databaseMatrix = [ + [ 'stripe_customer', 'varchar', '64' ], + [ 'stripe_subscription', 'varchar', '64' ], + [ 'subscription_price_id', 'varchar', '64' ], + [ 'current_period_end', 'int', '10' ], + [ 'current_period_start', 'int', '10' ], + [ 'status', 'varchar', '64' ], + [ 'local_user_id', 'int', '10' ], + [ 'billing_frequency', 'varchar', '16' ], + ]; + + public function __construct() { + parent::__construct(); + if ( ! self::$loaded ) { + self::$products = new MembershipProducts; + $api_key = Config::getValue( 'memberships/stripeSecret' ); + if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) { + Debug::error( "Memberships:__construct No Stripe Key found" ); + } else { + self::$stripe = new \Stripe\StripeClient( $api_key ); + } + self::$loaded = true; + } + } + + public function filter( $postArray, $params = [] ) { + foreach ( $postArray as $instance ) { + if ( !is_object( $instance ) ) { + $instance = $postArray; + $end = true; + } + $instance->name = self::$user->getUsername( $instance->local_user_id ); + $priceData = self::$products->findByPriceID( $instance->subscription_price_id ); + if ( $priceData ) { + if ( $priceData->stripe_price_monthly == $instance->subscription_price_id ) { + $price = $priceData->monthly_price; + } else { + $price = $priceData->yearly_price; + } + $instance->productName = $priceData->name; + $instance->prettyPrice = '$' . number_format( $price / 100, 2 ); // Outputs: $99.49 + } else { + $instance->prettyPrice = "unknown"; + $instance->productName = "unknown"; + } + $out[] = $instance; + if ( !empty( $end ) ) { + $out = $out[0]; + break; + } + } + return $out; + } + + public function getUserSubs( $limit = null ) { + $whereClause = ['local_user_id', '=', App::$activeUser->ID ]; + if ( empty( $limit ) ) { + $postData = self::$db->get( $this->tableName, $whereClause ); + } else { + $postData = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] ); + } + if ( !$postData->count() ) { + Debug::info( 'No user subs found.' ); + return false; + } + return $this->filter( $postData->results() ); + } + + public function findByUserID( $d ) { + $data = self::$db->get( $this->tableName, [ 'local_user_id', '=', $d ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function findActiveByUserID( $d ) { + $data = self::$db->get( $this->tableName, [ 'local_user_id', '=', $d, 'AND', 'status', '=', 'active' ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function cancel( $id ) { + $data = self::$db->get( $this->tableName, [ 'ID', '=', $id, 'AND', 'local_user_id', '=', App::$activeUser->ID, 'AND', 'status', '=', 'active' ] ); + if ( ! $data->count() ) { + return false; + } + $membershipID = $data->first()->stripe_subscription; + $out = false; + try { + $out = self::$stripe->subscriptions->cancel($membershipID, []); + } catch(\Exception $e) { + Debug::error( 'Exception' ); + Debug::v( $e ); + } + return $out; + } + + public function findBySubscriptionID( $d ) { + $data = self::$db->get( $this->tableName, [ 'stripe_subscription', '=', $d ] ); + if ( ! $data->count() ) { + return false; + } + return $data->first(); + } + + public function create( $customer, $subscription, $price, $start, $end, $status, $user_id, $frequency ) { + $fields = [ + 'stripe_customer' => $customer, + 'stripe_subscription' => $subscription, + 'subscription_price_id' => $price, + 'current_period_end' => $end, + 'current_period_start' => $start, + 'status' => $status, + 'local_user_id' => $user_id, + 'billing_frequency' => $frequency, + ]; + + if ( !self::$db->insert( $this->tableName, $fields ) ) { + Debug::error( "Memberships: not created: $fields" ); + new CustomException( 'membershipsCreate' ); + return false; + } + + return true; + } + + public function update( $id, $start, $end, $status ) { + $fields = [ + 'current_period_end' => $end, + 'current_period_start' => $start, + 'status' => $status, + ]; + + if ( !self::$db->update( $this->tableName, $id, $fields ) ) { + Debug::error( "Memberships: not updated: " ); + Debug::v( $fields ); + new CustomException( 'membershipsUpdate' ); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/app/plugins/members/plugin.php b/app/plugins/members/plugin.php new file mode 100644 index 0000000..b347f47 --- /dev/null +++ b/app/plugins/members/plugin.php @@ -0,0 +1,263 @@ + + * @link https://TheTempusProject.com + * @license https://opensource.org/licenses/MIT [MIT LICENSE] + */ +namespace TheTempusProject\Plugins; + +use TheTempusProject\TheTempusProject as App; +use TheTempusProject\Classes\Plugin; +use Stripe\StripeClient; +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; + public static $memberships; + private static $loaded = false; + public $pluginName = 'TP Membership'; + public $configName = 'memberships'; + public $pluginAuthor = 'JoeyK'; + public $pluginWebsite = 'https://TheTempusProject.com'; + public $modelVersion = '1.0'; + public $pluginVersion = '3.0'; + public $pluginDescription = 'A simple plugin which adds a site wide membership system.'; + public $permissionMatrix = [ + 'memberAccess' => [ + 'pretty' => 'Access Member Areas', + 'default' => false, + ], + 'controlMemberships' => [ + 'pretty' => 'User can Access and Control user memberships.', + 'default' => false, + ], + ]; + public $admin_links = [ + [ + 'text' => ' Memberships', + 'url' => [ + [ + 'text' => ' Products', + 'url' => '{ROOT_URL}admin/products', + ], + [ + 'text' => ' Subscriptions', + 'url' => '{ROOT_URL}admin/records', + ], + [ + 'text' => ' Scripts', + 'url' => '{ROOT_URL}admin/members', + ], + ], + ], + ]; + public $main_links = [ + [ + 'text' => 'My Membership', + 'url' => '{ROOT_URL}member/index', + 'filter' => 'member', + ], + [ + 'text' => 'Subscribe', + 'url' => '{ROOT_URL}member/join', + 'filter' => 'nonmember', + ], + [ + 'text' => 'Upgrade', + 'url' => '{ROOT_URL}member/upgrade', + 'filter' => 'upgrade', + ], + ]; + public $resourceMatrix = [ + 'groups' => [ + [ + 'name' => 'Member', + 'permissions' => '{"adminAccess":false}', + ] + ], + ]; + public $configMatrix = [ + 'stripePublishable' => [ + 'type' => 'text', + 'pretty' => 'Stripe Publishable key', + 'default' => 'pk_xxxxxxxxxxxxxxx', + ], + 'stripeSecret' => [ + 'type' => 'text', + 'pretty' => 'Stripe Secret key', + 'default' => 'sk_xxxxxxxxxxxxxxx', + ], + ]; + public static $webhookEvents = [ + 'customer.subscription.created', + 'customer.subscription.updated', + 'customer.subscription.deleted', + 'customer.updated', + 'customer.deleted', + 'payment_method.automatically_updated', + 'invoice.payment_action_required', + 'invoice.payment_succeeded', + 'checkout.session.completed', + ]; + public static $userLinks = [ + "url" => "{ROOT_URL}member/manage", + "name" => "Subscriptions" + ]; + + public function __construct( $load = false ) { + 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, + ]; + } + + parent::__construct( $load ); + + if ( $this->checkEnabled() && App::$isLoggedIn ) { + if ( ! self::$loaded && $load ) { + App::$userCPlinks[] = (object) self::$userLinks; + App::$topNavRightDropdown .= '
+ {ADMIN_BREADCRUMBS} + +
{name}
+User | +{name} | +
---|---|
Status | +{status} | +
Billing Frequency | +{billing_frequency} | +
Started: | +{DTC}{current_period_end}{/DTC} | +
Ended: | +{DTC}{current_period_start}{/DTC} | +
Stripe Customer | ++ + + | +
Stripe Subscription | ++ + + | +
Stripe Subscription Price | ++ + + | +
+ {ADMIN_BREADCRUMBS} +
Orphans are stripe prices that have unique lookup_keys used by the membership system, but have no products currently saved.
+price id | +amount | +lookup key | +url | ++ |
---|---|---|---|---|
{price_id} | +{amount} | +{lookup_key} | ++ | + |
+ No results to show. + | +
+ {ADMIN_BREADCRUMBS} + +
+ {ADMIN_BREADCRUMBS} + +
+ {ADMIN_BREADCRUMBS} + +
{name}
+Name | +{name} | +
---|---|
Monthly Price | +{monthly_price} | +
Yearly Price | +{yearly_price} | +
Stripe Product | ++ + + | +
Stripe Price Yearly | ++ + + | +
Stripe Price Monthly | ++ + + | +
Description | +{description} | +
+ {ADMIN_BREADCRUMBS} +
-
+
- + Webhook Generation + +
- + Orphan Products + +
+ {ADMIN_BREADCRUMBS} +
id | +status | +enabled_events | +url | ++ |
---|---|---|---|---|
{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}
+ +Are You Sure You Want to Cancel?
++
+ 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. +
+Compare plans
++ | Free | +Monthly | +Yearly | +
---|---|---|---|
Add and Manage Bookmarks | ++ | + | + |
Extensions for all major browsers | ++ | + | + |
Access from any device | ++ | + | + |
Share bookmarks and folders | ++ | + | + |
Import/Export Features | ++ | + | + |
Customizable Dashboards / Pages | ++ | + | + |
Request/Influence Development | ++ | + | + |
Early Access | ++ | + | + |
Cheaper | ++ | + | + |
Free
+$0/mo
+-
+
- Add / Manage your bookmarks +
- Extensions for all major browsers +
- Access from any device +
- Share access with anyone +
Monthly
+{prettyPriceMonthly}/month
+-
+
- Import/Export Features +
- Integration with TempusTools App (WIP) +
- Customizable Dashboards / Pages +
- Direct control of Feature Development +
- Early Access to new features +
Yearly
+Manage Memberships
++
Name | +Status | +Price | +Start | +End | ++ | + |
---|---|---|---|---|---|---|
{productName} | +{status} | +{prettyPrice} | +{DTC=date}{current_period_start}{/DTC} | +{DTC=date}{current_period_end}{/DTC} | ++ + + + | ++ + + + | +
+ No results to show. + | +
Membership Benefits
++ First, let me say thank you for choosing to become a member! There are several great benefits exclusively for members. +
++ In addition to extra features for bookmark management like dashboards and import/export, you gain access to influence development. Suggestions gives users a direct way to make suggestions to me personally. By default, suggestions are not public, but I can comment on them to let you know what I think. + All respectful and reasonable suggestions go up for the entire community to see and comment on. There iis going to be a loyalty points system in the future to allow you to accrue points and use the points to vote for suggestions and features they like. But that's for another time. +
++ 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. +
+ +Are You Sure You Want to Pause?
++
+ 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. +
+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. +
+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. +
+Upgrade to a Yearly Plan
++ Save more and enjoy uninterrupted access to all features with our yearly plan! +
++ Upgrading now means you'll save nearly $40 a year compared to the monthly plan. + Stay committed and make the most of our service. +
+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. +
+Review Category: {name}
+Name: | +{name} | +
---|---|
Slug: | +{slug} | +
Created: | +{DTC}{createdAt}{/DTC} | +
+ {ADMIN_BREADCRUMBS} + +
+ {ADMIN_BREADCRUMBS} + +
ID | +Name | +Slug | ++ | + | + |
---|---|---|---|---|---|
{ID} | +{name} | +{slug} | ++ | + | + |
+ No results to show. + | +
Review: {title}
+Title: | +{title} | +
---|---|
Rating: | +{rating} | +
Created: | +{DTC}{createdAt}{/DTC} | +
Review: | +|
{review} | +
ID | +Title | +Rating | ++ | + | + |
---|---|---|---|---|---|
{ID} | +{title} | +{rating} | ++ | + | + |
+ No results to show. + | +
Review: {title}
+Title: | +{title} | +
---|---|
Rating: | +{rating} | +
Created: | +{DTC}{createdAt}{/DTC} | +
Review: | +|
{review} | +
Share a Review
++
We thank you for for taking the time to review our products. We do not edit or modify reviews in any way. You, as the customer have the ability to modify your own reviews.
+We read each and every review. You and the admin team both have the ability to comment on reviews privately. Neither your reviews or comments will be publicly shared without your permission.
+ +Edit Your Review
++ +
+
+ Understanding the customer is a huge part of making products better. Whether its feedback that we are doing great, or we need work; the review allows users to share that with us. +
++ On this page you can find your reviews to see any responses or make edits. +
+Title | +Rating | ++ | + |
---|---|---|---|
{title} | +{rating} | ++ | + |
+ No results to show. + | +
Review: {title}
+Title: | +{title} | +
---|---|
Rating: | +{rating} | +
Created: | +{DTC}{createdAt}{/DTC} | +
Review: | +|
{review} | +
Subscribe
+Subscribe
+ +
Make a suggestion
++ I can't be expected to come up with all the great ideas around here. Feel free to make a suggestion here. Some suggestions will be approved for the community to comment on! +
+ ++ {ADMIN_BREADCRUMBS} + +
+ {ADMIN_BREADCRUMBS} + +
+ {ADMIN_BREADCRUMBS} + +
{title}
+Title | +{title} | +
---|---|
Progress | +{progress} | +
Started: | +{DTC}{startDate}{/DTC} | +
Description | +{description} | +
Work in Progress
++ {LOOP} +
{title}
+ Started: {prettyStart} ++
None Found
+This is just a simple message to say thank you for installing The Tempus Project. If you have any questions you can find everything through our website here.
', - 'author' => 1, - 'created{time}' => 0, - 'edited{time}' => 0, - 'draft' => 0, - ], - ]; - public $contact_footer_links = [ - [ - 'text' => 'Bug Report', - 'url' => '{ROOT_URL}bugreport', - ], - ]; - public function __construct() { - $reflect = new ReflectionClass( $this ); - if ( true === self::$initialized || !Installer::pluginEnabled( $reflect->getShortName() ) ) { - return; - } - foreach ( $this->contact_footer_links as $key => $link ) { - Navigation::addLink( App::CONTACT_FOOTER_MENU_NAME, $link ); - } - foreach ( $this->info_footer_links as $key => $link ) { - Navigation::addLink( App::INFO_MENU_NAME, $link ); - } - foreach ( $this->main_links as $key => $link ) { - Navigation::addLink( App::MAIN_MENU_NAME, $link ); - } - foreach ( $this->admin_links as $key => $link ) { - Navigation::addLink( App::ADMIN_MENU_NAME, $link ); - } - self::$initialized = true; - } -} diff --git a/app/resources/templates/example.tpl b/app/resources/templates/example.tpl deleted file mode 100644 index 0769ead..0000000 --- a/app/resources/templates/example.tpl +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - -This is a more complex component that includes another component...
-{simple} -...right in the middle!
\ No newline at end of file diff --git a/app/resources/views/example.html b/app/resources/views/example.html deleted file mode 100644 index e546b1e..0000000 --- a/app/resources/views/example.html +++ /dev/null @@ -1,141 +0,0 @@ - -It is comprised entirely of free form HTML
-If you are feeling extra bold you can use the templating engine to set variables to be replaced at runtime such as {variable} or even {variable2}, or maybe even just look through some data:
-{loop} -{value1} is the first value.-{value2} is the second value.
-{value3} is the third value.
-{/loop} -{ALT}No Loop{/ALT} -{footer} - - - - - - - - -
This is a default view
- -The Tempus-Project template-processor works with the backend to render html for the end user. This process has several steps and components that can get pretty complex. For the moment let's just review a few that can be used to generate web pages.
- -Views
-Views are the most basic interaction between the front-end and back-end. Inside of a controler, you can call a view in two ways:
-1. Normal - Views::view
- -2. Inline - Views::simpleView
- - - -Components
-You can think of components as a sort of front-end variable that can be filled in by the back-end before being send to the end-user. You can include components in all template parsing with a simple command:
- -Adding this to a controler will give access to that component's value in the rendering engine. For example: - - -Pagination
-PAGINATION - if (Pagination::totalPages() <= 1) { - Components::set('PAGINATION', 'Navigation
- -Filters
-In some cases, you may want to hide or show text on a page conditionally. For example, you may have administrator controls on a commonly used page. Obviously you would like to hide those controls from regular users; even if you have safeguards to prevent them from performing any restricted actions.
-This is where filters come in. They do exactly that, conditionally hide or show part of a page based on back-end logic. The admin example is so common, its already built in. If a user has the isAdmin permission on thier group, they will be able to see anything within the "ADMIN" tag:
-- Filters::add('member', '#{MEMBER}(.*?){/MEMBER}#is', (self::$isMember ? '$1' : ''), true); - Filters::add('mod', '#{MOD}(.*?){/MOD}#is', (self::$isMod ? '$1' : ''), true); - Filters::add('admin', '#{ADMIN}(.*?){/ADMIN}#is', (self::$isAdmin ? '$1' : ''), true); -- - - - - - - - '#\[b\](.*?)\[/b\]#is' => '$1', - '#\[p\](.*?)\[/p\]#is' => '
$1
', - '#\[i\](.*?)\[/i\]#is' => '$1', - '#\[u\](.*?)\[/u\]#is' => '$1', - '#\[s\](.*?)\[/s\]#is' => '$1
',
- '#\[color=(.*?)\](.*?)\[/color\]#is' => "$2",
- '#\[img\](.*?)\[/img\]#is' => "$2", - '#\(c\)#is' => '✔', - '#\(x\)#is' => '✖', - '#\(!\)#is' => '❕', - '#\(\?\)#is' => '❔', - '#\[list\](.*?)\[/list\]#is' => '
- $1
Issues
-One of the pre-existing filters happens to be Issues. In the controller for this file, you should see a block that includes several examples of Issues. These issues are automatically added as individual components and hidden with the issues filter.
-
- Issues::add( 'error', [ 'This is an error with multiple parts.' => [ 'Error 1', 'Error 2' ] ] ); - Issues::add( 'error', 'This is a single error.' ); - Issues::add( 'success', [ 'This is a success with multiple parts.' => [ 'Success 1', 'Success 2' ] ] ); - Issues::add( 'success', 'This is a single success.' ); - Issues::add( 'notice', 'This is a single notice.' ); - Issues::add( 'info', 'This is a single info.' ); - Filters::add('issues', '#{ISSUES}(.*?){/ISSUES}#is', (Issues::hasIssues() ? '$1' : ''), true); - Components::set( 'NOTICE', $test ); - Components::set( 'SUCCESS', $test ); - Components::set( 'ERROR', $test ); - Components::set( 'INFO', $test ); -- - - - - - - - - - - - - - - -