Make Block System Modular

The block system now works off a basic modularity/expansion principle where users can add their own blocks
This commit is contained in:
Julian Prieber 2024-06-20 12:24:54 +02:00
parent 80491edbb2
commit c1a47556f5
36 changed files with 565 additions and 334 deletions

View File

@ -1,169 +1,55 @@
<?php <?php
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\LinkType; use App\Models\LinkType;
use App\Models\Link; use App\Models\Link;
use App\Models\Button; use App\Models\Button;
use micro\FormFactory; use Illuminate\Support\Facades\Route;
use DB;
class LinkTypeViewController extends Controller class LinkTypeViewController extends Controller
{ {
public function getParamForm($typename, $linkId = 0)
public function getParamForm($typeid, $linkId = 0)
{ {
$linkType = LinkType::select('params', 'typename')->where('id', $typeid)->First(); $data = [
'link_title' => '',
'link_url' => '',
$data['params'] = ''; 'button_id' => 0,
$data['link_title'] = ''; 'buttons' => [],
$data['link_url'] = ''; ];
$data['button_id'] = 0;
if ($linkId) { if ($linkId) {
$link = Link::find($linkId); $link = Link::find($linkId);
$data['params'] = json_decode($link['type_params']); $typename = $link->type ?? 'predefined';
$data['link_title'] = $link->title; $data['link_title'] = $link->title;
$data['link_url'] = $link->link; $data['link_url'] = $link->link;
if (Route::currentRouteName() != 'showButtons') {$data['button_id'] = $link->button_id;} if (Route::currentRouteName() != 'showButtons') {
$data['button_id'] = $link->button_id;
}
// Check if type_params is not empty and is a valid JSON string
if (!empty($link->type_params) && is_string($link->type_params)) {
// Decode the JSON string into an associative array
$typeParams = json_decode($link->type_params, true);
if (is_array($typeParams)) {
// Merge the associative array into $data
$data = array_merge($data, $typeParams);
}
}
} }
if ($typename === 'predefined') {
if (!empty($linkType) && $linkType->typename === 'predefined') {
// get buttons list if showing predefined form
$buttons = Button::select()->orderBy('name', 'asc')->get(); $buttons = Button::select()->orderBy('name', 'asc')->get();
foreach ($buttons as $btn) { foreach ($buttons as $btn) {
$data['buttons'][] = [ $data['buttons'][] = [
'name' => $btn->name, 'name' => $btn->name,
'title' => $btn->alt, 'title' => $btn->alt,
'exclude' => $btn->exclude, 'exclude' => $btn->exclude,
'selected' => (is_object($data['params']) && $data['params']->button === $btn->name) 'selected' => ($linkId && isset($link) && $link->button_id == $btn->id),
]; ];
} }
//echo "<pre>"; print_r($data['params']); exit; return view('components.pageitems.predefined-form', $data);
} }
return view('components.pageitems.'. $linkType->typename.'-form', $data);
return view($typename . '.form', $data);
$jsonForm = FormFactory::jsonForm();
try {
$json = $linkType->params;
} catch (\Throwable $th) {
//throw $th;
}
// dynamiclly create params for predefined website to fill a select list with available buttons
if (!empty($linkType) && $linkType->typename === 'predefined') {
$buttons = Button::select('name')->orderBy('name', 'asc')->get();
$pdParams[] = ['tag' => 'select', 'name' => 'button', 'id'=> 'button'];
foreach ($buttons as $btn) {
$pdParams[0]['value'][] = [
'tag'=>'option',
'label' => ucwords($btn->name),
'value' => $btn->name
];
}
$pdParams[] = ['tag' => 'input', 'name' => 'link_title', 'id' => 'link_title', 'name' => 'link_title', 'tip' => 'Leave blank for default title'];
$pdParams[] = ['tag' => 'input', 'name' => 'link_url', 'id' => 'link_url', 'name' => 'link_url', 'tip' => 'Enter the url address for this site.'];
$json = json_encode($pdParams);
}
if (empty($json)) {
$json =
<<<EOS
[{
"tag": "input",
"id": "link_title",
"for": "link_title",
"label": "Link Title *",
"type": "text",
"name": "link_title",
"class": "form-control",
"tip": "Enter a title for this link",
"required": "required"
},
{
"tag": "input",
"id": "link",
"for": "link",
"label": "Link Address *",
"type": "text",
"name": "link_title",
"class": "form-control",
"tip": "Enter the website address",
"required": "required"
}
]
EOS;
}
if ($linkId) {
$link = Link::find($linkId);
}
// cleanup json
$params = json_decode($json, true);
$idx = 0;
foreach ($params as $p) {
if (!array_key_exists('for', $p))
$params[$idx]['for'] = $p['name'];
if (!array_key_exists('label', $p))
$params[$idx]['label'] = ucwords(preg_replace('/[^a-zA-Z0-9-]/', ' ', $p['name']));
if (!array_key_exists('label', $p) || !str_contains($p['class'], 'form-control')) {
$params[$idx]['class'] = " form-control";
}
// get existing values if any
if ($link) {
$typeParams = json_decode($link['type_params']);
//echo "<pre>";
// print_r($typeParams);
//print_r($params[$idx]);
//echo "</pre>";
if ($typeParams && property_exists($typeParams, $params[$idx]['name'])) {
if (key_exists('value', $params[$idx]) && is_array($params[$idx]['value'])) {
$optIdx = 0;
foreach ($params[$idx]['value'] as $option) {
//echo $option['value']."<br />";
//echo $typeParams->{$params[$idx]['name']};
if ($option['value'] == $typeParams->{$params[$idx]['name']}) {
$params[$idx]['value'][$optIdx]['selected'] = true;
break;
}
//echo $key ." = ".$value;
$optIdx++;
}
} else {
$params[$idx]['value'] = $typeParams->{$params[$idx]['name']};
}
}
}
$idx++;
}
$json = json_encode($params);
echo $jsonForm->render($json);
} }
} }

View File

@ -103,7 +103,27 @@ class UserController extends Controller
return abort(404); return abort(404);
} }
$links = DB::table('links')->join('buttons', 'buttons.id', '=', 'links.button_id')->select('links.link', 'links.id', 'links.button_id', 'links.title', 'links.custom_css', 'links.custom_icon', 'buttons.name')->where('user_id', $id)->orderBy('up_link', 'asc')->orderBy('order', 'asc')->get(); $links = DB::table('links')
->join('buttons', 'buttons.id', '=', 'links.button_id')
->select('links.*', 'buttons.name') // Assuming 'links.*' to fetch all columns including 'type_params'
->where('user_id', $id)
->orderBy('up_link', 'asc')
->orderBy('order', 'asc')
->get();
// Loop through each link to decode 'type_params' and merge it into the link object
foreach ($links as $link) {
if (!empty($link->type_params)) {
// Decode the JSON string into an associative array
$typeParams = json_decode($link->type_params, true);
if (is_array($typeParams)) {
// Merge the associative array into the link object
foreach ($typeParams as $key => $value) {
$link->$key = $value;
}
}
}
}
return view('linkstack.linkstack', ['userinfo' => $userinfo, 'information' => $information, 'links' => $links, 'littlelink_name' => $littlelink_name]); return view('linkstack.linkstack', ['userinfo' => $userinfo, 'information' => $information, 'links' => $links, 'littlelink_name' => $littlelink_name]);
} }
@ -152,20 +172,11 @@ class UserController extends Controller
'LinkTypes' => LinkType::get(), 'LinkTypes' => LinkType::get(),
'LinkData' => $linkData, 'LinkData' => $linkData,
'LinkID' => $id, 'LinkID' => $id,
'linkTypeID' => "1", 'linkTypeID' => "predefined",
'title' => "Predefined Site", 'title' => "Predefined Site",
]; ];
if (Route::currentRouteName() != 'showButtons' && $link = DB::table('links')->where('id', $id)->first()) { $data['typename'] = $link->type ?? 'predefined';
$bidToLinkTypeId = [
1 => "2", 2 => "2", 42 => "3", 43 => "4", 93 => "5", 6 => "6", 7 => "6", 44 => "7", 96 => "8",
];
$data['linkTypeID'] = $bidToLinkTypeId[$link->button_id] ?? "1";
$data['title'] = LinkType::where('id', $data['linkTypeID'])->value('title');
}
$data['SelectedLinkType'] = $data['LinkTypes']->firstWhere('typename', $linkData['typename']);
return view('studio/edit-link', $data); return view('studio/edit-link', $data);
} }
@ -173,82 +184,78 @@ class UserController extends Controller
//Save add link //Save add link
public function saveLink(Request $request) public function saveLink(Request $request)
{ {
$request->validate([ // Step 1: Validate Request
'link' => 'sometimes|exturl', // $request->validate([
]); // 'link' => 'sometimes|url',
// ]);
$linkType = LinkType::find($request->linktype_id);
$LinkTitle = ($request->link_text ?? $request->link_title) ?? $request->title;
$LinkURL = $request->link_url ?? $request->link;
$OrigLink = Link::find($request->linkid);
$customParams = [];
foreach ($request->all() as $key => $param) {
if (str_starts_with($key, "_") || in_array($key, ['linktype_id', 'linktype_title', 'link_text', 'link_url'])) continue;
$customParams[$key] = $param;
}
// Step 2: Determine Link Type and Title
$linkType = LinkType::findByTypename($request->typename);
$LinkTitle = $request->title;
$LinkURL = $request->link;
// Step 3: Load Link Type Logic
if($request->typename == 'predefined') {
$button = Button::where('name', $request->button)->first();
$linkData = [
'link' => $LinkURL,
'title' => $LinkTitle ?? $button?->alt,
'user_id' => Auth::user()->id,
'button_id' => $button?->id,
'type' => $request->typename // Save the link type
];
} else {
$linkTypePath = base_path("blocks/{$linkType->typename}/handler.php");
if (file_exists($linkTypePath)) {
include $linkTypePath;
$linkData = handleLinkType($request, $linkType);
$linkData['type'] = $linkType->typename; // Ensure 'type' is included in $linkData
} else {
abort(404, "Link type logic not found.");
}
}
// Step 4: Handle Custom Parameters
// (Same as before)
// Step 5: User and Button Information
$userId = Auth::user()->id; $userId = Auth::user()->id;
$button = Button::where('name', $request->button)->first(); $button = Button::where('name', $request->button)->first();
if ($button && empty($LinkTitle)) $LinkTitle = $button->alt; if ($button && empty($LinkTitle)) $LinkTitle = $button->alt;
if ($linkType->typename == 'video' && empty($LinkTitle)) { // Step 6: Prepare Link Data
$embed = OEmbed::get($LinkURL); // (Handled by the included file)
if ($embed) $LinkTitle = $embed->data()['title'];
} // Step 7: Save or Update Link
$OrigLink = Link::find($request->linkid);
$message = (ucwords($button?->name) ?? ucwords($linkType->typename)) . " has been "; $linkColumns = Schema::getColumnListing('links'); // Get all column names of links table
$filteredLinkData = array_intersect_key($linkData, array_flip($linkColumns)); // Filter $linkData to only include keys that are columns in the links table
$linkData = [
'link' => $LinkURL, // Combine remaining variables into one array and convert to JSON for the type_params column
'title' => $LinkTitle, $customParams = array_diff_key($linkData, $filteredLinkData);
'user_id' => $userId,
'button_id' => $button?->id // Check if $linkType->custom_html is defined and not null
]; if (isset($linkType->custom_html)) {
// Add $linkType->custom_html to the $customParams array
if ($linkType->typename == "link" && $customParams['GetSiteIcon'] == "1") { $customParams['custom_html'] = $linkType->custom_html;
$linkData['button_id'] = "2"; }
} elseif ($linkType->typename == "link") {
$linkData['button_id'] = "1"; $filteredLinkData['type_params'] = json_encode($customParams);
} elseif ($linkType->typename == "spacer") {
$linkData['title'] = $customParams['height'] ?? null;
$linkData['button_id'] = "43";
} elseif ($linkType->typename == "heading") {
$linkData['button_id'] = "42";
} elseif ($linkType->typename == "text") {
$sanitizedText = $request->text;
$sanitizedText = strip_tags($sanitizedText, '<a><p><strong><i><ul><ol><li><blockquote><h2><h3><h4>');
$sanitizedText = preg_replace("/<a([^>]*)>/i", "<a $1 rel=\"noopener noreferrer nofollow\">", $sanitizedText);
$sanitizedText = strip_tags_except_allowed_protocols($sanitizedText);
$linkData['title'] = $sanitizedText;
$linkData['button_id'] = "93";
} elseif (in_array($linkType->typename, ["email", "telephone"])) {
$linkData['button_id'] = $button?->id;
} elseif ($linkType->typename == "vcard") {
$data = $request->only([
'prefix', 'first_name', 'middle_name', 'last_name', 'suffix', 'nickname',
'organization', 'vtitle', 'role', 'work_url', 'email', 'work_email',
'home_phone', 'work_phone', 'cell_phone', 'home_address_label', 'home_address_street',
'home_address_city', 'home_address_state', 'home_address_zip', 'home_address_country',
'work_address_label', 'work_address_street', 'work_address_city', 'work_address_state',
'work_address_zip', 'work_address_country'
]);
$linkData['link'] = json_encode($data);
$linkData['button_id'] = 96;
}
if ($OrigLink) { if ($OrigLink) {
$OrigLink->update($linkData); $currentValues = $OrigLink->getAttributes();
$message .= "updated"; $nonNullFilteredLinkData = array_filter($filteredLinkData, function($value) {return !is_null($value);});
$updatedValues = array_merge($currentValues, $nonNullFilteredLinkData);
$OrigLink->update($updatedValues);
$message = "Link updated";
} else { } else {
$links = new Link($linkData); $link = new Link($filteredLinkData);
$links->user_id = $userId; $link->user_id = $userId;
$links->save(); $link->save();
$links->order = ($links->id - 1); $message = "Link added";
$links->save();
$message .= "added";
} }
// Step 8: Redirect
$redirectUrl = $request->input('param') == 'add_more' ? 'studio/add-link' : 'studio/links'; $redirectUrl = $request->input('param') == 'add_more' ? 'studio/add-link' : 'studio/links';
return Redirect($redirectUrl)->with('success', $message); return Redirect($redirectUrl)->with('success', $message);
} }

View File

@ -9,7 +9,7 @@ class Link extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = ['link', 'title', 'button_id', 'type_params', 'typename']; protected $fillable = ['link', 'title', 'button_id', 'type_params', 'type'];
protected static function boot() protected static function boot()
{ {

View File

@ -1,13 +1,97 @@
<?php <?php
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Collection;
use Illuminate\Filesystem\Filesystem;
class LinkType extends Model class LinkType extends Model
{ {
use HasFactory; protected $fillable = ['id', 'typename', 'title', 'description', 'icon', 'custom_html'];
protected $fillable = ['typename', 'title', 'description', 'icon', 'params']; // Assuming no database interaction, we can disable timestamps
public $timestamps = false;
/**
* Get all LinkTypes from the config.yml files in each subfolder of the blocks directory.
*
* @return Collection
*/
public static function get()
{
$blocksPath = base_path('blocks/');
$directories = (new Filesystem)->directories($blocksPath);
$linkTypes = collect();
// Prepend "predefined" entry to the $linkTypes list
$predefinedLinkType = new self([
'id' => 1,
'typename' => 'predefined',
'title' => null,
'description' => null,
'icon' => 'bi bi-boxes',
'custom_html' => false,
]);
$linkTypes->prepend($predefinedLinkType);
foreach ($directories as $dir) {
$configPath = $dir . '/config.yml';
if (file_exists($configPath)) {
$configData = Yaml::parse(file_get_contents($configPath));
// Create a new instance of LinkType for each config file
$linkType = new self([
'id' => $configData['id'] ?? 0,
'typename' => $configData['typename'] ?? null,
'title' => $configData['title'] ?? null,
'description' => $configData['description'] ?? null,
'icon' => $configData['icon'] ?? null,
'custom_html' => $configData['custom_html'] ?? [],
]);
$linkTypes->push($linkType);
}
}
$custom_order = [
'predefined',
'link',
'vcard',
'email',
'telephone',
'heading',
'spacer',
'text',
];
$sorted = $linkTypes->sortBy(function ($item) use ($custom_order) {
$index = array_search($item->typename, $custom_order);
return $index !== false ? $index : count($custom_order);
});
return $sorted->values();
}
/**
* Check if a LinkType with the given typename exists.
*
* @param string $typename
* @return bool
*/
public static function existsByTypename($typename)
{
return self::get()->contains('typename', $typename);
}
/**
* Find a LinkType by its typename.
*
* @param string $typename
* @return LinkType|null
*/
public static function findByTypename($typename)
{
return self::get()->firstWhere('typename', $typename);
}
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator; use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\View;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -41,5 +42,6 @@ class AppServiceProvider extends ServiceProvider
$allowed_schemes = ['http', 'https', 'mailto', 'tel']; $allowed_schemes = ['http', 'https', 'mailto', 'tel'];
return in_array(parse_url($value, PHP_URL_SCHEME), $allowed_schemes, true); return in_array(parse_url($value, PHP_URL_SCHEME), $allowed_schemes, true);
}); });
View::addNamespace('blocks', base_path('blocks'));
} }
} }

5
blocks/email/config.yml Normal file
View File

@ -0,0 +1,5 @@
id: 6
typename: email
title: "Custom Link"
icon: "bi bi-envelope-fill"
custom_html: false

19
blocks/email/handler.php Normal file
View File

@ -0,0 +1,19 @@
<?php
/**
* Handles the logic for "email" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
// Prepare the link data
$linkData = [
'title' => $request->title,
'button_id' => "6",
'link' => $request->link,
];
return $linkData;
}

View File

@ -0,0 +1,5 @@
id: 3
typename: heading
title: "Custom Link"
icon: "bi bi-card-heading"
custom_html: true

View File

@ -0,0 +1 @@
<div class="fadein"><h2>{{ $link->title }}</h2></div>

View File

@ -1,4 +1,2 @@
<label for='title' class='form-label'>{{__('messages.Heading Text:')}}</label> <label for='title' class='form-label'>{{__('messages.Heading Text:')}}</label>
<input type='text' name='title' value='{{$link_title}}' class='form-control' /> <input type='text' name='title' value='{{$link_title}}' class='form-control' />

View File

@ -0,0 +1,20 @@
<?php
/**
* Handles the logic for "heading" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
// Prepare the link data
$linkData = [
'title' => $request->title,
'button_id' => "42",
'var1' => "1",
'var2' => "1",
];
return $linkData;
}

5
blocks/link/config.yml Normal file
View File

@ -0,0 +1,5 @@
id: 2
typename: link
title: "Custom Link"
icon: "bi bi-link"
custom_html: false

25
blocks/link/handler.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/**
* Handles the logic for "link" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
if ($request->GetSiteIcon == "1") {
$buttonID = "2";
} else {
$buttonID = "1";
}
// Prepare the link data
$linkData = [
'title' => $request->title,
'button_id' => $buttonID,
];
return $linkData;
}

5
blocks/spacer/config.yml Normal file
View File

@ -0,0 +1,5 @@
id: 4
typename: spacer
title: "Custom Link"
icon: "bi bi-distribute-vertical"
custom_html: true

18
blocks/spacer/handler.php Normal file
View File

@ -0,0 +1,18 @@
<?php
/**
* Handles the logic for "spacer" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
// Prepare the link data
$linkData = [
'title' => $request->height ?? null,
'button_id' => "43",
];
return $linkData;
}

View File

@ -0,0 +1,5 @@
id: 7
typename: telephone
title: "Custom Link"
icon: "bi bi-telephone-fill"
custom_html: false

View File

@ -0,0 +1,18 @@
<?php
/**
* Handles the logic for "telephone" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
// Prepare the link data
$linkData = [
'title' => $request->title,
'button_id' => "44",
];
return $linkData;
}

5
blocks/text/config.yml Normal file
View File

@ -0,0 +1,5 @@
id: 5
typename: text
title: "Custom Link"
icon: "bi bi-fonts"
custom_html: true

View File

@ -0,0 +1 @@
<div class="fadein"><span style="">@if(env('ALLOW_USER_HTML') === true){!! $link->title !!}@else{{ $link->title }}@endif</span></div>

28
blocks/text/handler.php Normal file
View File

@ -0,0 +1,28 @@
<?php
/**
* Handles the logic for "text" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
// Sanitize the text input
$sanitizedText = $request->text;
$sanitizedText = strip_tags($sanitizedText, '<a><p><strong><i><ul><ol><li><blockquote><h2><h3><h4>');
$sanitizedText = preg_replace("/<a([^>]*)>/i", "<a $1 rel=\"noopener noreferrer nofollow\">", $sanitizedText);
// Assuming strip_tags_except_allowed_protocols is a custom function defined elsewhere
// This function should sanitize the text further by removing all tags except those allowed
// and ensuring all protocols in href attributes are safe.
$sanitizedText = strip_tags_except_allowed_protocols($sanitizedText);
// Prepare the link data
$linkData = [
'title' => $sanitizedText,
'button_id' => "93", // Assuming '93' is a predefined ID for a "text" button
];
return $linkData;
}

4
blocks/vcard/config.yml Normal file
View File

@ -0,0 +1,4 @@
id: 8
typename: vcard
title: "Custom Link"
icon: "bi bi-person-square"

29
blocks/vcard/handler.php Normal file
View File

@ -0,0 +1,29 @@
<?php
/**
* Handles the logic for "vcard" link type.
*
* @param \Illuminate\Http\Request $request The incoming request.
* @param mixed $linkType The link type information.
* @return array The prepared link data.
*/
function handleLinkType($request, $linkType) {
// Extract the necessary data from the request
$data = $request->only([
'prefix', 'first_name', 'middle_name', 'last_name', 'suffix', 'nickname',
'organization', 'vtitle', 'role', 'work_url', 'email', 'work_email',
'home_phone', 'work_phone', 'cell_phone', 'home_address_label', 'home_address_street',
'home_address_city', 'home_address_state', 'home_address_zip', 'home_address_country',
'work_address_label', 'work_address_street', 'work_address_city', 'work_address_state',
'work_address_zip', 'work_address_country'
]);
// Prepare the link data
$linkData = [
'title' => $request->link_title,
'link' => json_encode($data), // Encode the vCard data as JSON
'button_id' => "96",
];
return $linkData;
}

View File

@ -16,6 +16,7 @@ return [
'paths' => [ 'paths' => [
resource_path('views'), resource_path('views'),
base_path('themes'), base_path('themes'),
base_path('blocks'),
], ],
/* /*

View File

@ -43,7 +43,7 @@ class ButtonSeeder extends Seeder
], ],
[ [
"name" => "buy me a coffee", "name" => "coffee",
"alt" => "Buy Me a Coffee", "alt" => "Buy Me a Coffee",
"exclude" => false, "exclude" => false,
"group" => "default", "group" => "default",

View File

@ -1,5 +0,0 @@
<div class='button-heading'>
<h2>{{$link->title}}</h2>
</div>

View File

@ -7,4 +7,7 @@ if(trim(file_get_contents(base_path("version.json"))) < '4.0.0'){
fclose($handleFile); fclose($handleFile);
} }
} catch (Exception $e) {} } catch (Exception $e) {}
} }
?>
{{-- update links table to new structure --}}
@include('components.updater.links-table-translation-layer')

View File

@ -0,0 +1,63 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use App\Models\Link;
// First, check if the 'type' and 'type_params' columns exist, and add them if they don't
if (!Schema::hasColumn('links', 'type')) {
Schema::table('links', function (Blueprint $table) {
$table->string('type')->nullable();
});
}
if (!Schema::hasColumn('links', 'type_params')) {
Schema::table('links', function (Blueprint $table) {
$table->text('type_params')->nullable();
});
}
// Disable execution time limit
set_time_limit(0);
// Check version
if (trim(file_get_contents(base_path("version.json"))) < '4.8.1') {
// Get all links
$links = Link::all();
foreach ($links as $link) {
$type = null;
// Assign type based on button_id
switch ($link->button_id) {
case "1":
case "2":
$type = "link";
break;
case "43":
$type = "spacer";
break;
case "42":
$type = "heading";
break;
case "93":
$type = "text";
break;
case "44":
$type = "telephone";
break;
case "6":
$type = "email";
break;
case "96":
$type = "vcard";
break;
}
// Update the link if a type was assigned
if ($type !== null) {
$link->type = $type;
$link->save();
}
}
}

View File

@ -3,52 +3,38 @@
@php $initial = 1; @endphp @php $initial = 1; @endphp
@foreach($links as $link) @foreach($links as $link)
@php $linkName = str_replace('default ','',$link->title) @endphp @if(isset($link->custom_html) && $link->custom_html)
@switch($link->name) @include('blocks::' . $link->name . '.display', ['link' => $link])
@case('icon') @else
@break @switch($link->name)
@case('phone') @case('icon')
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-default button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}"><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/phone{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/')}}phone.svg @endif"></i>{{ $link->title }}</a></div>
@break
@case('default email')
@case('default email_alt')
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-default button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}"><img alt="email" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/email{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/')}}email.svg @endif"></i>{{ $link->title }}</a></div>
@break
@case('buy me a coffee')
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-coffee button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/coffee{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/')}}coffee.svg @endif">Buy me a Coffee</a></div>
@break
@case('space')
@php $title = $link->title; if (is_numeric($title)) { echo str_repeat("<br>", $title < 10 ? $title : 10); } else { echo "<br><br><br>"; } @endphp
@break
@case('heading')
<div class="fadein"><h2>{{ $link->title }}</h2></div>
@break
@case('text')
<div class="fadein"><span style="">@if(env('ALLOW_USER_HTML') === true){!! $link->title !!}@else{{ $link->title }}@endif</span></div>
@break
@case('vcard')
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-default button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ route('vcard') . '/' . $link->id }}"><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/vcard{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/')}}vcard.svg @endif"></i>{{ $link->title }}</a></div>
@break @break
@case('custom') @case('vcard')
@if($link->custom_css === "" or $link->custom_css === "NULL" or (theme('allow_custom_buttons') == "false")) <div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-default button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ route('vcard') . '/' . $link->id }}"><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/vcard{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/')}}vcard.svg @endif"></i>{{ $link->title }}</a></div>
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><i style="color: {{$link->custom_icon}}" class="icon hvr-icon fa {{$link->custom_icon}}"></i>{{ $link->title }}</a></div> @break
@break @case('phone')
@elseif($link->custom_css != "") <div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-default button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}"><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/phone{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/')}}phone.svg @endif"></i>{{ $link->title }}</a></div>
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom button-click button-hover icon-hover" style="{{ $link->custom_css }}" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><i style="color: {{$link->custom_icon}}" class="icon hvr-icon fa {{$link->custom_icon}}"></i>{{ $link->title }}</a></div> @break
@break @case('custom')
@endif @if($link->custom_css === "" or $link->custom_css === "NULL" or (theme('allow_custom_buttons') == "false"))
@case('custom_website') <div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><i style="color: {{$link->custom_icon}}" class="icon hvr-icon fa {{$link->custom_icon}}"></i>{{ $link->title }}</a></div>
@if($link->custom_css === "" or $link->custom_css === "NULL" or (theme('allow_custom_buttons') == "false")) @break
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom_website button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(file_exists(base_path("assets/favicon/icons/").localIcon($link->id))){{url('assets/favicon/icons/'.localIcon($link->id))}}@else{{getFavIcon($link->id)}}@endif" onerror="this.onerror=null; this.src='{{asset('assets/linkstack/icons/website.svg')}}';">{{ $link->title }}</a></div> @elseif($link->custom_css != "")
@break <div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom button-click button-hover icon-hover" style="{{ $link->custom_css }}" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><i style="color: {{$link->custom_icon}}" class="icon hvr-icon fa {{$link->custom_icon}}"></i>{{ $link->title }}</a></div>
@elseif($link->custom_css != "") @break
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom_website button-click button-hover icon-hover" style="{{ $link->custom_css }}" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(file_exists(base_path("assets/favicon/icons/").localIcon($link->id))){{url('assets/favicon/icons/'.localIcon($link->id))}}@else{{getFavIcon($link->id)}}@endif" onerror="this.onerror=null; this.src='{{asset('assets/linkstack/icons/website.svg')}}';">{{ $link->title }}</a></div> @endif
@break @case('custom_website')
@endif @if($link->custom_css === "" or $link->custom_css === "NULL" or (theme('allow_custom_buttons') == "false"))
@default <div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom_website button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(file_exists(base_path("assets/favicon/icons/").localIcon($link->id))){{url('assets/favicon/icons/'.localIcon($link->id))}}@else{{getFavIcon($link->id)}}@endif" onerror="this.onerror=null; this.src='{{asset('assets/linkstack/icons/website.svg')}}';">{{ $link->title }}</a></div>
<?php include base_path('config/button-names.php'); $newLinkName = $linkName; $isNewName = "false"; foreach($buttonNames as $key => $value) { if($newLinkName == $key) { $newLinkName = $value; $isNewName = "true"; }} ?> @break
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-{{ $link->name }} button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/{{$link->name}}{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/') . $link->name }}.svg @endif">@if($isNewName == "true"){{ ucfirst($newLinkName) }}@else{{ ucfirst($newLinkName) }}@endif</a></div> @elseif($link->custom_css != "")
@endswitch <div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-custom_website button-click button-hover icon-hover" style="{{ $link->custom_css }}" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(file_exists(base_path("assets/favicon/icons/").localIcon($link->id))){{url('assets/favicon/icons/'.localIcon($link->id))}}@else{{getFavIcon($link->id)}}@endif" onerror="this.onerror=null; this.src='{{asset('assets/linkstack/icons/website.svg')}}';">{{ $link->title }}</a></div>
@break
@endif
@default
<div style="--delay: {{ $initial++ }}s" class="button-entrance"><a id="{{ $link->id }}" class="button button-{{ $link->name }} button-click button-hover icon-hover" rel="noopener noreferrer nofollow noindex" href="{{ $link->link }}" @if((UserData::getData($userinfo->id, 'links-new-tab') != false))target="_blank"@endif ><img alt="{{ $link->name }}" class="icon hvr-icon" src="@if(theme('use_custom_icons') == "true"){{ url('themes/' . $GLOBALS['themeName'] . '/extra/custom-icons')}}/{{str_replace('default ','',$link->name)}}{{theme('custom_icon_extension')}} @else{{ asset('\/assets/linkstack/icons\/') . str_replace('default ','',$link->name) }}.svg @endif">{{ $link->title }}</a></div>
@endswitch
@endif
@endforeach @endforeach
<script> <script>

View File

@ -47,7 +47,7 @@
@endforeach @endforeach
</div> --}} </div> --}}
<input type='hidden' name='linktype_id' value='{{$linkTypeID}}'> <input type='hidden' name='typename' value='{{$typename}}'>
</div> </div>
</div> </div>
@ -111,20 +111,12 @@
<x-modal title="{{__('messages.Select Block')}}" id="SelectLinkType"> <x-modal title="{{__('messages.Select Block')}}" id="SelectLinkType">
<div class="d-flex flex-row flex-wrap p-3"> <div class="d-flex flex-row flex-wrap p-3">
@foreach ($LinkTypes as $lt)
@php
$custom_order = [1, 2, 8, 6, 7, 3, 4, 5,];
$sorted = $LinkTypes->sortBy(function ($item) use ($custom_order) {
return array_search($item['id'], $custom_order);
});
@endphp
@foreach ($sorted as $lt)
@php @php
$title = __('messages.block.title.'.$lt['typename']); $title = __('messages.block.title.'.$lt['typename']);
$description = __('messages.block.description.'.$lt['typename']); $description = __('messages.block.description.'.$lt['typename']);
@endphp @endphp
<a href="#" data-dismiss="modal" data-typeid="{{$lt['id']}}" data-typename="{{$title}}" class="hvr-grow m-2 w-100 d-block doSelectLinkType"> <a href="#" data-dismiss="modal" data-typeid="{{$lt['typename']}}" data-typename="{{$title}}" class="hvr-grow m-2 w-100 d-block doSelectLinkType">
<div class="rounded mb-3 shadow-lg"> <div class="rounded mb-3 shadow-lg">
<div class="row g-0"> <div class="row g-0">
<div class="col-auto bg-light d-flex align-items-center justify-content-center p-3"> <div class="col-auto bg-light d-flex align-items-center justify-content-center p-3">
@ -163,10 +155,10 @@
@push("sidebar-scripts") @push("sidebar-scripts")
<script> <script>
$(function() { $(function() {
LoadLinkTypeParams($("input[name='linktype_id']").val() , $("input[name=linkid]").val()); LoadLinkTypeParams($("input[name='typename']").val() , $("input[name=linkid]").val());
$('.doSelectLinkType').on('click', function() { $('.doSelectLinkType').on('click', function() {
$("input[name='linktype_id']").val($(this).data('typeid')); $("input[name='typename']").val($(this).data('typeid'));
$("#btnLinkType").html($(this).data('typename')); $("#btnLinkType").html($(this).data('typename'));
LoadLinkTypeParams($(this).data('typeid'), $("input[name=linkid]").val()); LoadLinkTypeParams($(this).data('typeid'), $("input[name=linkid]").val());

View File

@ -45,7 +45,7 @@ if (isset($_COOKIE['LinkCount'])) {
@include('components.favicon') @include('components.favicon')
@include('components.favicon-extension') @include('components.favicon-extension')
<?php function strp($urlStrp){return str_replace(array('http://', 'https://'), '', $urlStrp);} ?> <?php if(!function_exists('strp')){function strp($urlStrp){return str_replace(array('http://', 'https://'), '', $urlStrp);}} ?>
<div class="conatiner-fluid content-inner mt-n5 py-0"> <div class="conatiner-fluid content-inner mt-n5 py-0">
<div class="row"> <div class="row">
@ -227,42 +227,63 @@ if (isset($_COOKIE['LinkCount'])) {
@csrf @csrf
<div class="form-group col-lg-8"> <div class="form-group col-lg-8">
@php @php
function iconLink($icon){ if (!function_exists('iconLink')) {
$iconLink = DB::table('links') function iconLink($icon) {
->where('user_id', Auth::id()) $iconLink = DB::table('links')
->where('title', $icon) ->where('user_id', Auth::id())
->where('button_id', 94) ->where('title', $icon)
->value('link'); ->where('button_id', 94)
if (is_null($iconLink)){ ->value('link');
return false; if (is_null($iconLink)){
} else { return false;
return $iconLink;}} } else {
function searchIcon($icon) return $iconLink;
{$iconId = DB::table('links')
->where('user_id', Auth::id())
->where('title', $icon)
->where('button_id', 94)
->value('id');
if(is_null($iconId)){return false;}else{return $iconId;}}
function iconclicks($icon){
$iconClicks = searchIcon($icon);
$iconClicks = DB::table('links')->where('id', $iconClicks)->value('click_number');
if (is_null($iconClicks)){return 0;}
else {return $iconClicks;}}
function icon($name, $label) {
echo '<div class="mb-3">
<label class="form-label">'.$label.'</label>
<span class="form-text" style="font-size: 90%; font-style: italic;">'.__('messages.Clicks').': '.iconclicks($name).'</span>
<div class="input-group">
<span class="input-group-text"><i class="fab fa-'.$name.'"></i></span>
<input type="url" class="form-control" name="'.$name.'" value="'.iconLink($name).'" />
'.(searchIcon($name) != NULL ? '<a href="'.route("deleteLink", searchIcon($name)).'" class="btn btn-danger"><i class="bi bi-trash-fill"></i></a>' : '').'
</div>
</div>';
} }
@endphp }
}
if (!function_exists('searchIcon')) {
function searchIcon($icon) {
$iconId = DB::table('links')
->where('user_id', Auth::id())
->where('title', $icon)
->where('button_id', 94)
->value('id');
if (is_null($iconId)){
return false;
} else {
return $iconId;
}
}
}
if (!function_exists('iconclicks')) {
function iconclicks($icon) {
$iconClicks = searchIcon($icon);
$iconClicks = DB::table('links')->where('id', $iconClicks)->value('click_number');
if (is_null($iconClicks)){
return 0;
} else {
return $iconClicks;
}
}
}
if (!function_exists('icon')) {
function icon($name, $label) {
echo '<div class="mb-3">
<label class="form-label">'.$label.'</label>
<span class="form-text" style="font-size: 90%; font-style: italic;">'.__('messages.Clicks').': '.iconclicks($name).'</span>
<div class="input-group">
<span class="input-group-text"><i class="fab fa-'.$name.'"></i></span>
<input type="url" class="form-control" name="'.$name.'" value="'.iconLink($name).'" />
'.(searchIcon($name) != NULL ? '<a href="'.route("deleteLink", searchIcon($name)).'" class="btn btn-danger"><i class="bi bi-trash-fill"></i></a>' : '').'
</div>
</div>';
}
}
@endphp
<style>input{border-top-right-radius: 0.25rem!important; border-bottom-right-radius: 0.25rem!important;}</style> <style>input{border-top-right-radius: 0.25rem!important; border-bottom-right-radius: 0.25rem!important;}</style>