feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_recaptcha/autoload.php';
include_once 'f_core/f_classes/class_thumb/ThumbLib.inc.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.account');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('paid_memberships,backend_email,backend_username,signup_domain_restriction,list_email_domains,signup_min_password,signup_max_password,email_change_captcha,keep_entries_open,user_image_max_size,user_image_allowed_extensions,user_image_width,user_image_height,activity_logging,file_favorites,file_rating,file_comments,channel_comments,file_respnses,approve_friends,file_counts,numeric_delimiter,channel_views,recaptcha_site_key,recaptcha_secret_key,affiliate_module,affiliate_tracking_id,affiliate_view_id,affiliate_maps_api_key,affiliate_token_script,affiliate_payout_figure,affiliate_payout_currency,affiliate_payout_units,affiliate_payout_share,affiliate_requirements_type,affiliate_requirements_min');
$logged_in = VLogin::checkFrontend(VHref::getKey('account'));
$membership_check = ($cfg["paid_memberships"] == 1 and $_SESSION["USER_ID"] > 0) ? VLogin::checkSubscription() : null;
$notice_message = ($_POST and $_GET["do"] == '') ? VUseraccount::doChanges() : null;
$user_key = $class_filter->clr_str($_SESSION["USER_KEY"]);
$files = new VFiles;
$smarty->assign('page_display', 'tpl_account');
switch ($_GET["s"]) {
case "account-menu-entry1":
case "account-menu-entry13":
$tpl_page = 'tpl_overview.tpl';
switch ($_GET["do"]) {
case "loading":$smarty->display('tpl_frontend/tpl_acct/tpl_overview_image.tpl');
break;
case "cancel":VUseraccount::cancelProfileImage();
break;
case "upload":VUseraccount::changeProfileImage($user_key);
break;
case "save":VUseraccount::saveProfileImage($user_key);
break;
case "make-affiliate":echo VAffiliate::affiliateRequest();
break;
case "make-affiliate-email":$html = $_POST ? VAffiliate::affiliateRequestEmail() : null;
break;
}
break;
case "account-menu-entry2":
$tpl_page = 'tpl_profile_setup.tpl';
break;
case "account-menu-entry3":
$tpl_page = '';
break;
case "account-menu-entry4":
$tpl_page = 'tpl_email_opts.tpl';
if ($_POST) {
switch ($_GET["do"]) {
case "emchange":VUseraccount::changeEmail();
break;
}
}
break;
case "account-menu-entry5":$tpl_page = 'tpl_activity.tpl';
break;
case "account-menu-entry6":
$tpl_page = 'tpl_manage_acct.tpl';
if ($_POST) {
switch ($_GET["do"]) {
case "cpass":VUseraccount::changePassword();
break;
case "purge":VUseraccount::purgeAccount();
break;
}
}
break;
}
if (!isset($_GET["s"]) and !isset($_GET["do"])) {
VAffiliate::allowRequest();
$smarty->assign('c_section', VHref::getKey("account"));
}
$section_menus = (intval($_SESSION["USER_ID"]) > 0) ? $smarty->assign('keep_entries_open', $_SESSION[$_SESSION["USER_KEY"] . '_list']) : null;
$display_section = ($_GET["s"] != '' and !isset($_GET["do"])) ? $smarty->display('tpl_frontend/tpl_acct/' . $tpl_page) : null;
$display_page = (!isset($_GET["s"]) and !isset($_GET["do"])) ? $class_smarty->displayPage('frontend', 'tpl_account', $error_message, $notice_message) : null;

View File

@@ -0,0 +1,8 @@
<?php
define("_ISVALID", true);
include_once "f_core/config.core.php";
// Affiliate system - basic implementation
echo "<h1>Affiliate Program</h1>";
echo "<p>Affiliate program functionality coming soon...</p>";
echo "<a href=\"/account\">← Back to Account</a>";
?>

View File

@@ -0,0 +1,83 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.account');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.view');
include_once $class_language->setLanguageFile('frontend', 'language.files.menu');
include_once $class_language->setLanguageFile('frontend', 'language.browse');
include_once $class_language->setLanguageFile('frontend', 'language.channel');
include_once $class_language->setLanguageFile('frontend', 'language.manage.channel');
include_once $class_language->setLanguageFile('frontend', 'language.files');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.userpage');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('image_player,video_player,audio_player,document_player,paid_memberships,file_favorites,file_playlists,file_watchlist,file_responses,user_subscriptions,public_channels,channel_bulletins,event_map,user_friends,user_blocking,approve_friends,activity_logging,channel_comments,ucc_limit,comment_min_length,comment_max_length,channel_backgrounds,channel_bg_allowed_extensions,channel_bg_max_size,file_favorites,file_rating,file_comments,file_views,channel_views,guest_view_channel,file_promo,comment_emoji');
$guest_chk = $_SESSION["USER_ID"] == '' ? VHref::guestPermissions('guest_view_channel', VHref::getKey("channels")) : null;
$membership_check = ($cfg["paid_memberships"] == 1 and $_SESSION["USER_ID"] > 0) ? VLogin::checkSubscription() : null;
$channel = new VChannel;
if (isset($_GET["a"])) {
switch ($_GET["a"]) {
case "postbulletin":
echo $ht = VChannel::postBulletin();
break;
case "hideuseractivity":
echo $ht = VChannel::hideActivity();
break;
case "cb-addfr": //add friends
case "cb-remfr": //remove friend
case "cb-block": //block friend
case "cb-unblock": //unblock friend
$notifier = new VNotify;
echo $do = $_POST ? VChannel::userActions($_GET["a"]) : null;
break;
}
}
if (isset($_GET["do"])) {
$vview = new VView;
switch ($_GET["do"]) {
case "sub-option":
echo $do_load = VView::subHtml('', 'channel');
break;
case "unsub-option":
echo $do_load = VView::subHtml(1, 'channel');
break;
case "sub-continue":
echo $do_load = VView::subContinue('channel');
break;
case "user-unsubscribe":
echo $do_load = VSubscriber::unsub_request((int) $_POST["uf_vuid"]);
break;
}
}
if (!isset($_GET["s"]) and !isset($_GET["do"]) and !isset($_GET["a"]) and $error_message == '' and isset($_SESSION["q"])) {$_SESSION["q"] = null;}
$update_views = (!isset($_GET["s"]) and !isset($_GET["do"]) and !isset($_GET["a"]) and $error_message == '') ? VChannel::updateViews() : null;
$display_page = (!isset($_GET["a"]) and !isset($_GET["do"])) ? $class_smarty->displayPage('frontend', 'tpl_channel', $error_message, $notice_message) : null;

View File

@@ -0,0 +1,105 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.home');
include_once $class_language->setLanguageFile('frontend', 'language.files.menu');
include_once $class_language->setLanguageFile('frontend', 'language.account');
include_once $class_language->setLanguageFile('frontend', 'language.userpage');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.view');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('paid_memberships,backend_email,backend_username,channel_comments,file_counts,numeric_delimiter,channel_views,user_subscriptions,user_friends,user_blocking,internal_messaging,approve_friends,channel_promo');
$guest_chk = $_SESSION["USER_ID"] == '' ? VHref::guestPermissions('guest_browse_channel', VHref::getKey("channels")) : null;
$membership_check = ($cfg["paid_memberships"] == 1 and $_SESSION["USER_ID"] > 0) ? VLogin::checkSubscription() : null;
$section = VHref::getKey('channels');
$channels = new VChannels;
if (isset($_GET["p"]) and (int) $_GET["p"] >= 0) {
//viewmode changer/loader
$view_mode = (int) $_GET["m"];
switch ($view_mode) {
case "1":
case "2":
echo $html = VChannels::viewMode_loader($view_mode);
break;
}
}
if (isset($_GET["a"])) {
switch ($_GET["a"]) {
case "sub":
$act = VChannels::chSubscribe();
break;
case "unsub":
$act = VChannels::chSubscribe(1);
break;
case "cb-addfr":
case "cb-remfr":
case "cb-block":
case "cb-unblock":
$act = VChannels::contactActions($_GET["a"]);
break;
case "cb-msg":
$act = VChannels::sessionMessageName();
break;
case "vm":
echo $ct = VChannels::viewMode();
break;
}
}
if (isset($_GET["do"])) {
if ($_GET["do"] == 'sub-option' or $_GET["do"] == 'unsub-option' or $_GET["do"] == 'sub-continue' or $_GET["do"] == 'user-sub' or $_GET["do"] == 'user-unsub') {
$vview = new VView;
}
switch ($_GET["do"]) {
case "subscribe":break;
case "user-unsubscribe":
echo $do_load = VSubscriber::unsub_request((int) $_POST["uf_vuid"]);
break;
case "sub-option":
echo $do_load = VView::subHtml(0, 'home');
break;
case "unsub-option":
echo $do_load = VView::subHtml(1, 'home');
break;
case "sub-continue":
echo $do_load = VView::subContinue('channels');
break;
}
}
if (!isset($_GET["sort"]) and !isset($_GET["s"]) and !isset($_GET["a"]) and !isset($_GET["p"]) and !isset($_GET["do"])) {
$smarty->assign('c_section', VHref::getKey("channels"));
$_SESSION["q"] = null;
}
echo $display_page = (!isset($_GET["sort"]) and !isset($_GET["s"]) and !isset($_GET["a"]) and !isset($_GET["p"]) and !isset($_GET["do"])) ? $class_smarty->displayPage('frontend', 'tpl_channels', $error_message, $notice_message) : null;

View File

@@ -0,0 +1,23 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$act = VSubscriber::get_live_viewers();

View File

@@ -0,0 +1,63 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.account');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.manage.channel');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('paid_memberships,backend_email,backend_username,channel_backgrounds,channel_comments,file_counts,numeric_delimiter,channel_views,channel_bg_max_size,channel_bg_allowed_extensions,user_subscriptions,user_friends,user_blocking,internal_messaging,approve_friends');
$logged_in = VLogin::checkFrontend(VHref::getKey('manage_channel'));
$membership_check = ($cfg["paid_memberships"] == 1 and $_SESSION["USER_ID"] > 0) ? VLogin::checkSubscription() : null;
$channel = new VChannel;
switch ($_GET["s"]) {
case "channel-menu-entry1": //general setup
echo ($_POST ? VChannel::postChanges('ch_setup') : VChannel::manage_general());
break;
case "channel-menu-entry2": //channel modules
echo ($_POST ? VChannel::postChanges('ch_modules') : VChannel::manage_modules());
break;
case "channel-menu-entry3": //channel art
if (isset($_GET["do"])) {
switch ($_GET["do"]) {
case "edit-crop":$method = 'edit_crop';
break;
case "edit-gcrop":$method = 'edit_crop';
break;
case "delete-crop":$method = 'html_delete_crop';
break;
}
} else {
$method = 'manage_art';
}
echo ($_POST ? VChannel::postChanges('ch_art') : VChannel::$method());
break;
case "channel-menu-entry4":
break;
}
if (!isset($_GET["s"]) and !isset($_GET["do"])) {
$smarty->assign('c_section', VHref::getKey("manage_channel"));
}
echo $display_page = (!isset($_GET["s"])) ? $class_smarty->displayPage('frontend', 'tpl_manage_channel', $error_message, $notice_message) : null;

View File

@@ -0,0 +1,74 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
define('_ISADMIN', true);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('backend', 'language.dashboard');
include_once $class_language->setLanguageFile('backend', 'language.settings.entries');
include_once $class_language->setLanguageFile('backend', 'language.subscriber');
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.account');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
$error_message = null;
$notice_message = null;
$ipn_check = false;
$cfg[] = $class_database->getConfigurations('paypal_test,paypal_test_email,affiliate_module,affiliate_tracking_id,affiliate_view_id,affiliate_maps_api_key,affiliate_token_script,affiliate_payout_figure,affiliate_payout_units,affiliate_payout_currency,affiliate_payout_share,sub_shared_revenue,subscription_payout_currency,channel_views,sub_threshold,partner_requirements_min,partner_requirements_type');
$logged_in = !$ipn_check ? VLogin::checkFrontend(VHref::getKey("subscribers")) : null;
$analytics = false;
if (isset($_GET["do"])) {
switch ($_GET["do"]) {
case "save-subscriber":
echo $ht = VAffiliate::affiliateProfile(1);
break;
case "showstats":
echo $ht = VSubscriber::userStats();
break;
case "make-partner":
case "clear-partner":
echo $ht = VAffiliate::affiliateRequest();
break;
case "make-partner-email":
case "clear-partner-email":
$ht = $_POST ? VAffiliate::affiliateRequestEmail() : null;
break;
}
}
if (!isset($_GET["s"]) and !isset($_GET["do"])) {
VAffiliate::allowRequest('partner');
$smarty->assign('c_section', VHref::getKey("subscribers"));
}
if (!isset($_GET["do"])) {
$smarty->assign('file_type', 'sub');
if (isset($_GET["rg"]) and isset($_SESSION["USER_PARTNER"]) and (int) $_SESSION["USER_PARTNER"] == 1) {
$smarty->assign('html_payouts', VSubscriber::html_payouts(1));
} else if (isset($_GET["rp"]) and isset($_SESSION["USER_PARTNER"]) and (int) $_SESSION["USER_PARTNER"] == 1) {
$smarty->assign('html_payouts', VSubscriber::html_payouts());
} else if ((isset($_GET["rg"]) and (!isset($_SESSION["USER_PARTNER"]) or (int) $_SESSION["USER_PARTNER"] == 0)) or (isset($_GET["rp"]) and (!isset($_SESSION["USER_PARTNER"]) or (int) $_SESSION["USER_PARTNER"] == 0))) {
header("Location: " . $cfg["main_url"] . '/' . VHref::getKey("subscribers"));
exit;
}
$class_smarty->displayPage('frontend', 'tpl_subscribers', $error_message, $notice_message);
}

View File

@@ -0,0 +1,26 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$error_message = null;
$notice_message = null;
$do_sync = VSubscriber::sync_df();

View File

@@ -0,0 +1,26 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$error_message = null;
$notice_message = null;
$do_sync = VSubscriber::sync_subs();

View File

@@ -0,0 +1,26 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$error_message = null;
$notice_message = null;
$do_sync = VSubscriber::sync_vods();

View File

@@ -0,0 +1,148 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
if (isset($_SERVER["HTTP_ORIGIN"]) === true) {
$origin = $_SERVER["HTTP_ORIGIN"];
$allowed_origins = array(
"http://192.168.100.77",
"http://192.168.100.77:3000",
);
if (in_array($origin, $allowed_origins, true) === true) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Methods: GET,POST');
header('Access-Control-Allow-Headers: VS-Custom-Header');
}
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
exit; // OPTIONS request wants only the policy, we can stop here
}
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('backend', 'language.members.entries');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$cfg = $class_database->getConfigurations('paypal_log_file,paypal_logging,paypal_test,paypal_email,paypal_test_email,backend_notification_payment,backend_email,backend_username');
$error_message = null;
$notice_message = null;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$out = array("valid" => 0);
$cc = $class_filter->clr_str($_POST["a"]);
$ff = $class_filter->clr_str($_POST["b"]);
$estr = $class_filter->clr_str($_POST["c"]);
$amount = $class_filter->clr_str($_POST["d"]);
$rs = $db->execute(sprintf("SELECT `db_id`, `channel_owner`, `channel_id`, `usr_id`, `usr_key`, `chat_user` FROM `db_livechat` WHERE `chat_id`='%s' AND `stream_id`='%s' LIMIT 1;", $cc, $ff));
if ($rs->fields["db_id"]) {
$ch_owner = $rs->fields["channel_owner"];
$ch_user = $rs->fields["chat_user"];
$ch_id = $rs->fields["channel_id"];
$usr_id = $rs->fields["usr_id"];
$usr_key = $rs->fields["usr_key"];
$p = $db->execute(sprintf("SELECT `usr_key`, `usr_photo`, `usr_profileinc`, `usr_tokencount` FROM `db_accountuser` WHERE `usr_id`='%s' LIMIT 1;", $usr_id));
if ($p->fields['usr_key']) {
$tokens = $p->fields['usr_tokencount'];
$pimg = VUseraccount::getProfileImage_inc($p->fields['usr_key'], $p->fields['usr_photo'], $p->fields['usr_profileinc']);
} else {
echo json_encode($out); return;
}
$cstr = md5($ff . $cc . $ch_user . $amount . $cfg["live_chat_salt"]);
if ($estr == $cstr and $amount <= $tokens) {
$db->execute(sprintf("UPDATE `db_accountuser` SET `usr_tokencount`=`usr_tokencount`-%s WHERE `usr_id`='%s' LIMIT 1;", $amount, $usr_id));
if ($db->Affected_Rows() > 0) {
$ins = array(
"tk_from" => $usr_id,
"tk_to" => $ch_id,
"tk_from_user" => $ch_user,
"tk_to_user" => $ch_owner,
"tk_amount" => $amount,
"tk_date" => date("Y-m-d H:i:s"),
);
$class_database->doInsert('db_tokendonations', $ins);
if ($db->Affected_Rows() > 0) {
/* mail notifications */
$notifier = new VNotify;
$website_logo = $smarty->fetch($cfg["templates_dir"] . '/tpl_frontend/tpl_header/tpl_headerlogo.tpl');
$user_data = VUserinfo::getUserInfo($ch_id);
/* user notification */
$_replace = array(
'##TITLE##' => $language["payment.notification.donate.subj"],
'##LOGO##' => $website_logo,
'##H2##' => $language["recovery.forgot.password.h2"] . $user_data["uname"] . ',',
'##NR##' => '<b>' . $amount . '</b>',
'##USER##' => '<a href="' . VHref::channelURL(["username" => $ch_user]) . '" target="_blank">' . $ch_user . '</a>',
'##YEAR##' => date('Y'),
);
$notifier->dst_mail = VUserinfo::getUserEmail($ch_id);
$notifier->dst_name = $user_data["uname"];
$notifier->Mail('frontend', 'token_donation_fe', $_replace);
$_output[] = $user_data["uname"] . ' -> token_donation_fe -> ' . $notifier->dst_mail . ' -> ' . date("Y-m-d H:i:s");
/* admin notification */
if ($cfg["backend_notification_payment"] == 1) {
include 'f_core/config.backend.php';
$main_url = $cfg["main_url"] . '/' . $backend_access_url;
$notifier->msg_subj = $language["payment.notification.donate.subj"];
$notifier->dst_mail = $cfg["backend_email"];
$notifier->dst_name = $cfg["backend_username"];
$user_data2 = VUserinfo::getUserInfo($ch_id);
$_replace = array(
'##TITLE##' => $notifier->msg_subj,
'##LOGO##' => $website_logo,
'##SUBJ##' => str_replace(array('##USER1##', '##USER2##', '##NR##'), array($ch_user, $user_data2["uname"], $amount), $language["payment.notification.donate.subj.be"]),
'##H2##' => $language["recovery.forgot.password.h2"] . $cfg["backend_username"] . ',',
'##USER1##' => '<a href="' . $main_url . '/' . VHref::getKey('be_members') . '?u=' . $usr_key . '" target="_blank">' . $ch_user . '</a>',
'##USER2##' => '<a href="' . $main_url . '/' . VHref::getKey('be_members') . '?u=' . $user_data2["key"] . '" target="_blank">' . $user_data2["uname"] . '</a>',
'##NR##' => '<b>' . $amount . '</b>',
'##YEAR##' => date('Y'),
);
$notifier->Mail('backend', 'token_donation_be', $_replace);
$_output[] = $cfg["backend_username"] . ' -> token_donation_be -> ' . $notifier->dst_mail . ' -> ' . date("Y-m-d H:i:s");
}
$log_mail = '.mailer.log';
VServer::logToFile($log_mail, implode("\n", $_output));
$out["pimg"] = $pimg; $out["valid"] = 1;
echo json_encode($out);
return;
}
}
}
}
echo json_encode($out);
return;
}
}

View File

@@ -0,0 +1,131 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
if (isset($_SERVER["HTTP_ORIGIN"]) === true) {
$origin = $_SERVER["HTTP_ORIGIN"];
$allowed_origins = array(
"http://192.168.100.77",
"http://192.168.100.77:3000",
);
if (in_array($origin, $allowed_origins, true) === true) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Methods: GET,POST');
header('Access-Control-Allow-Headers: VS-Custom-Header');
}
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
exit; // OPTIONS request wants only the policy, we can stop here
}
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('backend', 'language.members.entries');
$error_message = null;
$notice_message = null;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$rs = array();
$us = array();
$ls = array();
$cn = explode(',', $language["supported_currency_names"]);
$cu = explode(',', $language["supported_currency_codes"]);
$cid = $class_filter->clr_str($_POST["a"]);
$file_key = $class_filter->clr_str($_POST["b"]);
$get_token_nr = isset($_POST["c"]);
$ch = $db->execute(sprintf("SELECT A.`db_id`, A.`usr_id`, A.`usr_key`, B.`usr_tokencount` FROM `db_livechat` A, `db_accountuser` B WHERE A.`chat_id`='%s' AND A.`stream_id`='%s' AND A.`usr_id`=B.`usr_id` LIMIT 1;", $cid, $file_key));
if (!$ch->fields["db_id"] or ($ch->fields["db_id"] and $ch->fields["usr_id"] == 0)) {
$ls["l"] = "auth";
echo json_encode($ls);
return;
}
$usr_id = $ch->fields["usr_id"];
$us[0] = (int) $ch->fields["usr_tokencount"];
if ($get_token_nr) {
echo json_encode($us);
return;
}
$pp = $class_database->getConfigurations('paypal_log_file,paypal_logging,paypal_test,paypal_email,paypal_test_email');
$paypal_url = $pp["paypal_test"] == 0 ? 'https://www.paypal.com/cgi-bin/webscr' : 'https://www.sandbox.paypal.com/cgi-bin/webscr';
$paypal_mail = $pp["paypal_test"] == 0 ? $pp["paypal_email"] : $pp["paypal_test_email"];
$paypal_return = rawurlencode($cfg["main_url"] . '/' . VHref::getKey('watch') . '?l=' . $file_key . '&fst=1');
$paypal_cancel = rawurlencode($cfg["main_url"] . '/' . VHref::getKey('watch') . '?l=' . $file_key);
$paypal_ipn = rawurlencode($cfg["main_url"] . '/' . VHref::getKey('tokenpayment') . '?do=ipn');
$paypal_param = array(
"cmd" => "_xclick",
"rm" => 2,
"business" => $paypal_mail,
"return" => $paypal_return,
"cancel_return" => $paypal_cancel,
"notify_url" => $paypal_ipn,
"item_name" => "##ITEM_NAME##",
"item_number" => "##PP_ITEM##",
"currency_code" => "##PP_CURRENCY##",
"amount" => "##PP_AMOUNT##",
"custom" => "",
);
$params = array();
foreach ($paypal_param as $pk => $pv) {
$params[] = sprintf("%s=%s", $pk, $pv);
}
$paypal_link = sprintf("%s?%s", $paypal_url, implode("&", $params));
$tks = $db->execute(sprintf("SELECT `tk_id`, `tk_name`, `tk_slug`, `tk_price`, `tk_currency`, `tk_amount` FROM `db_livetoken` WHERE `tk_active`='1';"));
if ($tks->fields["tk_id"]) {
$i = 0;
while (!$tks->EOF) {
$rs[$i][0] = $tks->fields["tk_id"];
$rs[$i][1] = $tks->fields["tk_name"];
$rs[$i][2] = $tks->fields["tk_price"];
$rs[$i][3] = $tks->fields["tk_currency"];
$rs[$i][4] = $tks->fields["tk_amount"];
$paypal_string = $usr_id . '|' . $rs[$i][0] . '|' . $rs[$i][4];
$paypal_item = rawurlencode($paypal_string . '|' . md5($paypal_string . $cfg["global_salt_key"]));
$paypal_name = rawurlencode($cfg["website_shortname"] . ' - ' . $rs[$i][1]);
$paypal_search = array("##ITEM_NAME##", "##PP_ITEM##", "##PP_CURRENCY##", "##PP_AMOUNT##");
$paypal_replace = array($paypal_name, $paypal_item, $rs[$i][3], $rs[$i][2]);
$rs[$i][5] = str_replace($paypal_search, $paypal_replace, $paypal_link);
$ak = array_search($rs[$i][3], $cn);
$rs[$i][3] = $cu[$ak];
$i += 1;
$tks->MoveNext();
}
}
$ls["u"] = $us;
$ls["l"] = $rs;
echo json_encode($ls);
}
}

View File

@@ -0,0 +1,109 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('backend', 'language.members.entries');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$cfg = $class_database->getConfigurations('paypal_log_file,paypal_logging,paypal_test,paypal_email,paypal_test_email,backend_notification_payment,backend_email,backend_username');
$error_message = null;
$notice_message = null;
if ($_POST and isset($_GET["do"]) and $_GET["do"] == "ipn") {
$p = new VPaypalToken;
$p->paypal_url = $cfg["paypal_test"] == 1 ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr';
if ($p->validate_ipn()) {
$ipn_info = explode('|', urldecode($p->ipn_data["item_number"]));
$usr_id = (int) $ipn_info[0];
$item_id = (int) $ipn_info[1];
$item_amt = (int) $ipn_info[2];
$item_price = $class_filter->clr_str($p->ipn_data["mc_gross"]);
$txn_id = $class_filter->clr_str($p->ipn_data["txn_id"]);
$hash = $ipn_info[3];
$pps = $usr_id . '|' . $item_id . '|' . $item_amt;
if ($hash == md5($pps . $cfg["global_salt_key"])) {
$db->execute(sprintf("UPDATE `db_accountuser` SET `usr_tokencount`=`usr_tokencount`+%s WHERE `usr_id`='%s' LIMIT 1;", $item_amt, $usr_id));
if ($db->Affected_Rows() > 0) {
$receipt = null;
foreach ($p->ipn_data as $key => $value) {$receipt .= $key . ': ' . $value . '<br>';}
$ins_array = array(
'usr_id' => $usr_id,
'tk_id' => $item_id,
'tk_amount' => $item_amt,
'tk_price' => $item_price,
'tk_date' => date("Y-m-d H:i:s"),
'txn_id' => $txn_id,
'txn_receipt' => str_replace('<br>', "\n", $receipt),
);
$class_database->doInsert('db_tokenpayments', $ins_array);
/* mail notifications */
$notifier = new VNotify;
$website_logo = $smarty->fetch($cfg["templates_dir"] . '/tpl_frontend/tpl_header/tpl_headerlogo.tpl');
$user_data = VUserinfo::getUserInfo($usr_id);
/* user notification */
$_replace = array(
'##TITLE##' => $language["payment.notification.token.subj"],
'##LOGO##' => $website_logo,
'##H2##' => $language["recovery.forgot.password.h2"] . $user_data["uname"] . ',',
'##NR##' => $item_amt,
'##YEAR##' => date('Y'),
);
$notifier->dst_mail = VUserinfo::getUserEmail($usr_id);
$notifier->dst_name = $user_data["uname"];
$notifier->Mail('frontend', 'token_notification_fe', $_replace);
$_output[] = $user_data["uname"] . ' -> token_notification_fe -> ' . $notifier->dst_mail . ' -> ' . date("Y-m-d H:i:s");
/* admin notification */
if ($cfg["backend_notification_payment"] == 1) {
include 'f_core/config.backend.php';
$main_url = $cfg["main_url"] . '/' . $backend_access_url;
$notifier->msg_subj = $language["payment.notification.token.subj.be"] . urldecode($p->ipn_data["payer_email"]);
$notifier->dst_mail = $cfg["backend_email"];
$notifier->dst_name = $cfg["backend_username"];
$_replace = array(
'##TITLE##' => $notifier->msg_subj,
'##LOGO##' => $website_logo,
'##H2##' => $language["recovery.forgot.password.h2"] . $cfg["backend_username"] . ',',
'##USER##' => '<a href="' . $main_url . '/' . VHref::getKey('be_members') . '?u=' . $user_data["key"] . '" target="_blank">' . $user_data["uname"] . '</a>',
'##NR##' => $item_amt,
'##PAID##' => $p->ipn_data["mc_gross"] . $p->ipn_data["mc_currency"],
'##PAID_RECEIPT##' => urldecode($receipt),
'##YEAR##' => date('Y'),
);
$notifier->Mail('backend', 'token_notification_be', $_replace);
$_output[] = $cfg["backend_username"] . ' -> token_notification_be -> ' . $notifier->dst_mail . ' -> ' . date("Y-m-d H:i:s");
}
$log_mail = '.mailer.log';
VServer::logToFile($log_mail, implode("\n", $_output));
}
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
define('_ISADMIN', true);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('backend', 'language.dashboard');
include_once $class_language->setLanguageFile('backend', 'language.settings.entries');
include_once $class_language->setLanguageFile('backend', 'language.subscriber');
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.account');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
$error_message = null;
$notice_message = null;
$ipn_check = false;
$cfg[] = $class_database->getConfigurations('paypal_test,paypal_test_email,affiliate_module,affiliate_tracking_id,affiliate_view_id,affiliate_maps_api_key,affiliate_token_script,affiliate_payout_figure,affiliate_payout_units,affiliate_payout_currency,affiliate_payout_share,sub_shared_revenue,subscription_payout_currency,channel_views,sub_threshold,partner_requirements_min,partner_requirements_type,token_threshold');
$logged_in = !$ipn_check ? VLogin::checkFrontend(VHref::getKey("tokens")) : null;
$analytics = false;
if (isset($_GET["do"])) {
switch ($_GET["do"]) {
case "save-subscriber":
break;
case "showstats":
echo $ht = VToken::userStats();
break;
case "make-partner":
case "clear-partner":
break;
case "make-partner-email":
case "clear-partner-email":
break;
}
}
if (!isset($_GET["s"]) and !isset($_GET["do"])) {
VAffiliate::allowRequest('partner');
$smarty->assign('c_section', VHref::getKey("subscribers"));
}
if (!isset($_GET["do"])) {
$smarty->assign('file_type', 'sub');
if (isset($_GET["rg"]) and isset($_SESSION["USER_PARTNER"]) and (int) $_SESSION["USER_PARTNER"] == 1) {
$smarty->assign('html_payouts', VToken::html_payouts(1));
} else if (isset($_GET["rp"]) and isset($_SESSION["USER_PARTNER"]) and (int) $_SESSION["USER_PARTNER"] == 1) {
$smarty->assign('html_payouts', VToken::html_payouts());
} else if ((isset($_GET["rg"]) and (!isset($_SESSION["USER_PARTNER"]) or (int) $_SESSION["USER_PARTNER"] == 0)) or (isset($_GET["rp"]) and (!isset($_SESSION["USER_PARTNER"]) or (int) $_SESSION["USER_PARTNER"] == 0))) {
header("Location: " . $cfg["main_url"] . '/' . VHref::getKey("tokens"));
exit;
}
$class_smarty->displayPage('frontend', 'tpl_tokens', $error_message, $notice_message);
}

View File

@@ -0,0 +1,34 @@
<?php
defined('_ISVALID') or exit;
class VAuth {
public static function loginForm() {
global $smarty;
return $smarty->fetch('tpl_login.tpl');
}
public static function registerForm() {
global $smarty;
return $smarty->fetch('tpl_register.tpl');
}
public static function processLogin() {
$username = VSecurity::postParam('username', 'string');
$password = VSecurity::postParam('password', 'string');
if ($username && $password) {
// Basic authentication logic
global $class_database;
$sql = "SELECT * FROM db_accountuser WHERE usr_user = ? AND usr_password = ?";
$user = $class_database->execute($sql, [$username, md5($password)]);
if ($user && count($user) > 0) {
VSession::setUserSession($user[0]);
return ['success' => 'Login successful'];
}
}
return ['error' => 'Invalid credentials'];
}
}
?>

View File

@@ -0,0 +1,35 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once 'f_core/config.core.php';
$cfg = $class_database->getConfigurations('email_change_captcha_level,signup_captcha_level,frontend_username_recovery_captcha_level,frontend_password_recovery_captcha_level,backend_username_recovery_captcha_level,backend_password_recovery_captcha_level');
switch ($_GET["extra"]) {
case '':$_c = new VCaptcha('', $cfg["signup_captcha_level"]);
break;
case '1':$_c = new VCaptcha('recover_left', $cfg["frontend_password_recovery_captcha_level"]);
break;
case '2':$_c = new VCaptcha('recover_right', $cfg["frontend_username_recovery_captcha_level"]);
break;
case '3':$_c = new VCaptcha('recover_left', $cfg["backend_password_recovery_captcha_level"]);
break;
case '4':$_c = new VCaptcha('recover_right', $cfg["backend_username_recovery_captcha_level"]);
break;
case '5':$_c = new VCaptcha('change_email', $cfg["email_change_captcha_level"]);
break;
}

View File

@@ -0,0 +1 @@
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"critical","message":"Uncaught Exception: Non-static method VLogin::isLoggedIn() cannot be called statically in /srv/easystream/f_modules/m_frontend/m_auth/signin.php on line 81","context":{"exception_class":"Error","file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":81,"trace":"#0 {main}","previous":null},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.logger.php","line":395,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":117,"function":"critical","class":"VLogger"},{"file":"unknown","line":0,"function":"handleException","class":"VErrorHandler"}]}

View File

@@ -0,0 +1,26 @@
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"session_lifetime\" in /srv/easystream/f_core/f_classes/class.session.php on line 26","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.session.php","line":26,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":26,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/config.core.php","line":61,"function":"init","class":"VSession"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"session_name\" in /srv/easystream/f_core/f_classes/class.session.php on line 40","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.session.php","line":40,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":40,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/config.core.php","line":61,"function":"init","class":"VSession"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: ini_set(): session.name &quot;&quot; cannot be numeric or empty in /srv/easystream/f_core/f_classes/class.session.php on line 40","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.session.php","line":40,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"unknown","line":0,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":40,"function":"ini_set","class":null},{"file":"/srv/easystream/f_core/config.core.php","line":61,"function":"init","class":"VSession"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"session_lifetime\" in /srv/easystream/f_core/f_classes/class.session.php on line 63","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.session.php","line":63,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":63,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":50,"function":"_sessionInit","class":"VSession"},{"file":"/srv/easystream/f_core/config.core.php","line":61,"function":"init","class":"VSession"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"fe_lang\" in /srv/easystream/f_core/f_classes/class.session.php on line 83","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.session.php","line":83,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":83,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":50,"function":"_sessionInit","class":"VSession"},{"file":"/srv/easystream/f_core/config.core.php","line":61,"function":"init","class":"VSession"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined variable $backend_access_url in /srv/easystream/f_core/config.core.php on line 68","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/config.core.php","line":68,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/config.core.php","line":68,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined property: VFilter::$_allowDOMEvents in /srv/easystream/f_core/f_classes/class.filter.php on line 212","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":212,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":212,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":318,"function":"removeEvilAttributes","class":"VFilter"},{"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":332,"function":"sanitize","class":"VFilter"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":141,"function":"clr_str","class":"VFilter"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":56,"function":"getUserIP","class":"VIPaccess"},{"file":"/srv/easystream/f_core/config.core.php","line":68,"function":"sectionAccess","class":"VIPaccess"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined property: VFilter::$_allowDOMEvents in /srv/easystream/f_core/f_classes/class.filter.php on line 212","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":212,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":212,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":318,"function":"removeEvilAttributes","class":"VFilter"},{"file":"/srv/easystream/f_core/f_classes/class.filter.php","line":332,"function":"sanitize","class":"VFilter"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":56,"function":"clr_str","class":"VFilter"},{"file":"/srv/easystream/f_core/config.core.php","line":68,"function":"sectionAccess","class":"VIPaccess"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Trying to access array offset on value of type bool in /srv/easystream/f_core/f_classes/class.ipaccess.php on line 27","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":27,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":27,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":58,"function":"banIPrange_db","class":"VIPaccess"},{"file":"/srv/easystream/f_core/config.core.php","line":68,"function":"sectionAccess","class":"VIPaccess"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_ip_based_access\" in /srv/easystream/f_core/f_classes/class.ipaccess.php on line 69","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":69,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":69,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/config.core.php","line":68,"function":"sectionAccess","class":"VIPaccess"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"backend_ip_based_access\" in /srv/easystream/f_core/f_classes/class.ipaccess.php on line 70","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":70,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.ipaccess.php","line":70,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/config.core.php","line":68,"function":"sectionAccess","class":"VIPaccess"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"USER_ID\" in /srv/easystream/f_core/f_classes/class.session.php on line 139","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_core/f_classes/class.session.php","line":139,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_core/f_classes/class.session.php","line":139,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_core/config.core.php","line":71,"function":"isLoggedIn","class":"VSession"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":21,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php on line 199","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":199,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":199,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":24,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php on line 204","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":204,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":204,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":24,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php on line 205","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":205,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":205,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":24,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"custom_tagline\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php on line 383","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":383,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":383,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":24,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php on line 402","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":402,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":402,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":24,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php on line 465","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":465,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.global.php","line":465,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":24,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php on line 31","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php","line":31,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php","line":31,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":25,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php on line 38","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php","line":38,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php","line":38,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":25,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"website_shortname\" in /srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php on line 62","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php","line":62,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_data/data_languages/en_US/lang_frontend/language.signin.php","line":62,"function":"handleError","class":"VErrorHandler"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":25,"function":"include_once","class":null}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"paid_memberships\" in /srv/easystream/f_modules/m_frontend/m_auth/signin.php on line 30","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":30,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":30,"function":"handleError","class":"VErrorHandler"}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"gp_auth\" in /srv/easystream/f_modules/m_frontend/m_auth/signin.php on line 32","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":32,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":32,"function":"handleError","class":"VErrorHandler"}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"fb_auth\" in /srv/easystream/f_modules/m_frontend/m_auth/signin.php on line 56","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":56,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":56,"function":"handleError","class":"VErrorHandler"}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"frontend_global_submit\" in /srv/easystream/f_modules/m_frontend/m_auth/signin.php on line 74","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":74,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":74,"function":"handleError","class":"VErrorHandler"}]}
{"timestamp":"2025-10-18 05:10:14","request_id":"req_68f32136711d68.26462734","ip":"172.18.0.1","user_agent":"curl/8.14.1","request_uri":"/f_modules/m_frontend/m_auth/signin.php","request_method":"GET","user_id":null,"session_id":null,"memory_usage":2097152,"peak_memory":2097152,"execution_time":0.1830599308013916,"level":"warning","message":"Warning: Undefined array key \"login_remember\" in /srv/easystream/f_modules/m_frontend/m_auth/signin.php on line 80","context":{"error_type":"Warning","severity":2,"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":80,"context":[]},"backtrace":[{"file":"/srv/easystream/f_core/f_classes/class.errorhandler.php","line":95,"function":"log","class":"VLogger"},{"file":"/srv/easystream/f_modules/m_frontend/m_auth/signin.php","line":80,"function":"handleError","class":"VErrorHandler"}]}

View File

@@ -0,0 +1,164 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_facebook/Facebook/autoload.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.recovery');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('fb_auth,fb_app_id,fb_app_secret');
$_SESSION['FBRLH_' . 'state'] = $class_filter->clr_str($_GET['state']);
$fb = new Facebook\Facebook([
'app_id' => $cfg['fb_app_id'],
'app_secret' => $cfg['fb_app_secret'],
'default_graph_version' => 'v2.7',
'default_access_token' => '1061711193887319|fc3a99ba0d42b98b51ac3fa124268422',
]);
$helper = $fb->getRedirectLoginHelper();
try {
$u = $cfg["main_url"] . '/f_modules/m_frontend/m_auth/fb_callback_login.php';
$accessToken = $helper->getAccessToken($u);
} catch (Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch (Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
if (!isset($accessToken)) {
if ($helper->getError()) {
header('HTTP/1.0 401 Unauthorized');
echo "Error: " . $helper->getError() . "\n";
echo "Error Code: " . $helper->getErrorCode() . "\n";
echo "Error Reason: " . $helper->getErrorReason() . "\n";
echo "Error Description: " . $helper->getErrorDescription() . "\n";
} else {
header('HTTP/1.0 400 Bad Request');
echo 'Bad request';
}
exit;
}
// Logged in
// The OAuth 2.0 client handler helps us manage access tokens
$oAuth2Client = $fb->getOAuth2Client();
// Get the access token metadata from /debug_token
$tokenMetadata = $oAuth2Client->debugToken($accessToken);
// Validation (these will throw FacebookSDKException's when they fail)
$tokenMetadata->validateAppId($cfg['fb_app_id']); // Replace {app-id} with your app id
// If you know the user ID this access token belongs to, you can validate it here
$tokenMetadata->validateExpiration();
$user_id = $tokenMetadata->getUserId();
if (!$accessToken->isLongLived()) {
// Exchanges a short-lived access token for a long-lived one
try {
$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
} catch (Facebook\Exceptions\FacebookSDKException $e) {
echo "<p>Error getting long-lived access token: " . $helper->getMessage() . "</p>\n\n";
exit;
}
}
$_SESSION['fb_access_token'] = (string) $accessToken;
try {
// Returns a `Facebook\FacebookResponse` object
$response = $fb->get('/me?fields=id,about,cover,email,first_name,picture.width(300).height(300),last_name,name', $accessToken);
} catch (Facebook\Exceptions\FacebookResponseException $e) {
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch (Facebook\Exceptions\FacebookSDKException $e) {
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
$user = $response->getGraphUser();
$go = true;
if ($user_id and $go) {
$rs = $db->execute(sprintf("SELECT `usr_id` FROM `db_accountuser` WHERE `oauth_provider`='facebook' AND `oauth_uid`='%s' LIMIT 1;", $user_id));
if ($rs->fields['usr_id']) {
$usr_id = $rs->fields['usr_id'];
$reguser = VUserinfo::getUserInfo($usr_id);
} else {
if (isset($user['birthday']->date)) {
$a = $user['birthday']->date;
$b = explode(' ', $a);
$birthday = $b[0];
} else {
$birthday = '0000-00-00';
}
$uu = explode('@', $user['email']);
$_SESSION["fb_user"] = array(
'id' => $user_id,
'email' => $user['email'],
'name' => $user['name'],
'picture' => $user['picture']['url'],
'username' => $uu[0],
'gender' => 'M',
'country' => (isset($user['user_location']->name) ? $user['user_location']->name : ''),
'city' => (isset($user['user_location']->hometown) ? $user['user_location']->hometown : ''),
'birth_date' => $birthday,
'status' => 1,
);
echo VSignup::auth_register_ajax();
exit;
}
if ($reguser['key'] != '') {
$_SESSION["USER_ID"] = $usr_id;
$_SESSION["USER_NAME"] = $reguser['uname'];
$_SESSION["USER_KEY"] = $reguser['key'];
$_SESSION["USER_DNAME"] = $reguser['dname'];
$login_update = VLogin::updateOnLogin($usr_id);
$log_activity = ($cfg["activity_logging"] == 1 and $action = new VActivity($usr_id, 0)) ? $action->addTo('log_signin') : null;
$loc = $cfg["main_url"] . '/' . VHref::getKey('account');
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
} else {
die('Account suspended!');
}
}

View File

@@ -0,0 +1,152 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_facebook/Facebook/autoload.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.recovery');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$error_message = null;
$notice_message = null;
$class_database->getConfigurations('fb_auth,fb_app_id,fb_app_secret');
$_SESSION['FBRLH_' . 'state'] = $class_filter->clr_str($_GET['state']);
$fb = new Facebook\Facebook([
'app_id' => $cfg['fb_app_id'],
'app_secret' => $cfg['fb_app_secret'],
'default_graph_version' => 'v2.7',
'default_access_token' => '1061711193887319|fc3a99ba0d42b98b51ac3fa124268422',
]);
$helper = $fb->getRedirectLoginHelper();
try {
$u = $cfg["main_url"] . '/f_modules/m_frontend/m_auth/fb_callback_register.php';
$accessToken = $helper->getAccessToken($u);
} catch (Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch (Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
if (!isset($accessToken)) {
if ($helper->getError()) {
header('HTTP/1.0 401 Unauthorized');
echo "Error: " . $helper->getError() . "\n";
echo "Error Code: " . $helper->getErrorCode() . "\n";
echo "Error Reason: " . $helper->getErrorReason() . "\n";
echo "Error Description: " . $helper->getErrorDescription() . "\n";
} else {
header('HTTP/1.0 400 Bad Request');
echo 'Bad request';
}
exit;
}
// Logged in
// The OAuth 2.0 client handler helps us manage access tokens
$oAuth2Client = $fb->getOAuth2Client();
// Get the access token metadata from /debug_token
$tokenMetadata = $oAuth2Client->debugToken($accessToken);
// Validation (these will throw FacebookSDKException's when they fail)
$tokenMetadata->validateAppId($cfg['fb_app_id']); // Replace {app-id} with your app id
// If you know the user ID this access token belongs to, you can validate it here
$tokenMetadata->validateExpiration();
$user_id = $tokenMetadata->getUserId();
if (!$accessToken->isLongLived()) {
// Exchanges a short-lived access token for a long-lived one
try {
$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
} catch (Facebook\Exceptions\FacebookSDKException $e) {
echo "<p>Error getting long-lived access token: " . $helper->getMessage() . "</p>\n\n";
exit;
}
}
$_SESSION['fb_access_token'] = (string) $accessToken;
try {
// Returns a `Facebook\FacebookResponse` object
$response = $fb->get('/me?fields=id,about,birthday,context,cover,currency,education,email,first_name,gender,hometown,picture.width(300).height(300),last_name,link,locale,location,middle_name,name,political,quotes,relationship_status,religion,significant_other,website', $accessToken);
} catch (Facebook\Exceptions\FacebookResponseException $e) {
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch (Facebook\Exceptions\FacebookSDKException $e) {
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
$user = $response->getGraphUser();
if (isset($user['birthday']->date)) {
$a = $user['birthday']->date;
$b = explode(' ', $a);
$birthday = $b[0];
} else {
$birthday = '0000-00-00';
}
$go = true;
if ($user_id and $go) {
$rs = $db->execute(sprintf("SELECT `usr_id` FROM `db_accountuser` WHERE `oauth_provider`='facebook' AND `oauth_uid`='%s' LIMIT 1;", $user_id));
if ($rs->fields['usr_id']) {
$usr_id = $rs->fields['usr_id'];
$_SESSION["USER_ERROR"] = $language["notif.error.accout.linked"];
$loc = $cfg["main_url"] . '/' . VHref::getKey('signin') . '?f=';
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
exit;
} else {
$_SESSION["fb_user"] = array(
'id' => $user_id,
'email' => $user['email'],
'name' => $user['name'],
'gender' => ($user['gender'] == 'male' ? 'M' : 'F'),
'country' => (isset($user['user_location']->name) ? $user['user_location']->name : ''),
'city' => (isset($user['user_location']->hometown) ? $user['user_location']->hometown : ''),
'birth_date' => $birthday,
'status' => 1,
);
$loc = $cfg["main_url"] . '/' . VHref::getKey('signup') . '?u=' . $user_id;
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
exit;
}
}

View File

@@ -0,0 +1,108 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_google/Google_Client.php';
include_once 'f_core/f_classes/class_google/contrib/Google_Oauth2Service.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.recovery');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('gp_auth,gp_app_id,gp_app_secret,list_reserved_users');
$clientId = $cfg['gp_app_id'];
$clientSecret = $cfg['gp_app_secret'];
$redirectUrl = $cfg['main_url'] . '/f_modules/m_frontend/m_auth/gp_callback_login.php';
$homeUrl = $cfg['main_url'];
$gClient = new Google_Client();
$gClient->setClientId($clientId);
$gClient->setClientSecret($clientSecret);
$gClient->setRedirectUri($redirectUrl);
$google_oauthV2 = new Google_Oauth2Service($gClient);
if (isset($_GET['code'])) {
$gClient->authenticate();
$_SESSION['token'] = $gClient->getAccessToken();
header('Location: ' . filter_var($redirectUrl, FILTER_SANITIZE_URL));
}
if (isset($_SESSION['token'])) {
$gClient->setAccessToken($_SESSION['token']);
}
if ($gClient->getAccessToken()) {
//logged in
$userProfile = $google_oauthV2->userinfo->get();
$user_id = $userProfile['id'];
if ($user_id) {
$rs = $db->execute(sprintf("SELECT `usr_id` FROM `db_accountuser` WHERE `oauth_provider`='google' AND `oauth_uid`='%s' LIMIT 1;", $user_id));
if ($rs->fields['usr_id']) {
$usr_id = $rs->fields['usr_id'];
$reguser = VUserinfo::getUserInfo($usr_id);
$loc = $cfg["main_url"] . '/' . VHref::getKey('account');
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
} else {
$uu = explode('@', $userProfile['email']);
$_SESSION["gp_user"] = array(
'id' => $user_id,
'email' => $userProfile['email'],
'name' => $userProfile['name'],
'picture' => $userProfile['picture'],
'username' => $uu[0],
'gender' => 'M',
'country' => '',
'city' => '',
'birth_date' => '0000-00-00',
'status' => 1,
);
echo VSignup::auth_register_ajax();
exit;
}
if ($reguser['key'] != '') {
$_SESSION["USER_ID"] = $usr_id;
$_SESSION["USER_NAME"] = $reguser['uname'];
$_SESSION["USER_KEY"] = $reguser['key'];
$_SESSION["USER_DNAME"] = $reguser['dname'];
$login_update = VLogin::updateOnLogin($usr_id);
$log_activity = ($cfg["activity_logging"] == 1 and $action = new VActivity($usr_id, 0)) ? $action->addTo('log_signin') : null;
$loc = $cfg["main_url"] . '/' . VHref::getKey('account');
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
} else {
die('Account suspended!');
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_google/Google_Client.php';
include_once 'f_core/f_classes/class_google/contrib/Google_Oauth2Service.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.recovery');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('gp_auth,gp_app_id,gp_app_secret');
$clientId = $cfg['gp_app_id'];
$clientSecret = $cfg['gp_app_secret'];
$redirectUrl = $cfg['main_url'] . '/f_modules/m_frontend/m_auth/gp_callback_register.php';
$homeUrl = $cfg['main_url'];
$gClient = new Google_Client();
$gClient->setClientId($clientId);
$gClient->setClientSecret($clientSecret);
$gClient->setRedirectUri($redirectUrl);
$google_oauthV2 = new Google_Oauth2Service($gClient);
if (isset($_GET['code'])) {
$gClient->authenticate();
$_SESSION['token'] = $gClient->getAccessToken();
header('Location: ' . filter_var($redirectUrl, FILTER_SANITIZE_URL));
}
if (isset($_SESSION['token'])) {
$gClient->setAccessToken($_SESSION['token']);
}
if ($gClient->getAccessToken()) {
//logged in
$user = $google_oauthV2->userinfo->get();
$user_id = $user['id'];
$user_email = $user['email'];
$user_name = $user['name'];
$user_gender = $user['gender'][0];
$user_gender = $user_gender == '' ? 'M' : $user_gender;
$user_bday = '0000-00-00';
$rs = $db->execute(sprintf("SELECT `usr_id` FROM `db_accountuser` WHERE `oauth_provider`='google' AND `oauth_uid`='%s' LIMIT 1;", $user_id));
if ($rs->fields['usr_id']) {
$usr_id = $rs->fields['usr_id'];
$_SESSION["USER_ERROR"] = $language["notif.error.accout.linked"];
$loc = $cfg["main_url"] . '/' . VHref::getKey('signin') . '?f=';
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
exit;
} else {
$_SESSION["gp_user"] = array(
'id' => $user_id,
'email' => $user_email,
'name' => $user_name,
'gender' => $user_gender,
'birth_date' => $user_bday,
'status' => 1,
);
$loc = $cfg["main_url"] . '/' . VHref::getKey('signup') . '?u=' . $user_id;
echo '<script type="text/javascript">opener.location="' . $loc . '"; window.close();</script>';
exit;
}
}

View File

@@ -0,0 +1,28 @@
<?php
if (!defined('_ISVALID')) define('_ISVALID', true);
include_once 'f_core/config.core.php';
// Handle login
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if ($username && $password) {
$sql = "SELECT * FROM db_accountuser WHERE usr_user = ? AND usr_password = MD5(?) AND usr_active = 1";
$user = $class_database->execute($sql, [$username, $password]);
if ($user && count($user) > 0) {
$_SESSION['usr_id'] = $user[0]['usr_id'];
$_SESSION['usr_user'] = $user[0]['usr_user'];
$_SESSION['usr_key'] = $user[0]['usr_key'];
echo "<script>alert('Login successful! Redirecting...'); window.location.href='/';</script>";
exit;
} else {
$error = 'Invalid credentials';
}
}
}
echo $class_smarty->displayPage('frontend', 'tpl_auth_login');
?>

View File

@@ -0,0 +1,116 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_recaptcha/autoload.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.recovery');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$error_message = null;
$notice_message = null;
$notifier = new VNotify;
$cfg = $class_database->getConfigurations('activity_logging,password_recovery_captcha,username_recovery_captcha,recovery_link_lifetime,allow_username_recovery,allow_password_recovery,backend_username_recovery,backend_password_recovery,backend_username_recovery_captcha,backend_password_recovery_captcha,backend_username,backend_email,noreply_email,recaptcha_site_key,recaptcha_secret_key');
$section_check = ($class_smarty->backendSectionCheck() == 1) ? 'backend' : 'frontend';
$logged_in = $section_check == 'frontend' ? VLogin::isLoggedIn('fe') : ($section_check == 'backend' ? VLogin::isLoggedIn('be') : null);
$rec_username = $_POST["rec_username"] != '' ? $class_filter->clr_str($_POST["rec_username"]) : null;
$rec_email = $_POST["rec_email"] != '' ? $class_filter->clr_str($_POST["rec_email"]) : null;
$left_captcha = (($cfg["password_recovery_captcha"] == 1 or $cfg["backend_password_recovery_captcha"] == 1) and $_POST["g-recaptcha-response"] != '') ? $class_filter->clr_str($_POST["g-recaptcha-response"]) : null;
$right_captcha = (($cfg["username_recovery_captcha"] == 1 or $cfg["backend_username_recovery_captcha"] == 1) and $_POST["g-recaptcha-response"] != '') ? $class_filter->clr_str($_POST["g-recaptcha-response"]) : null;
$pass_rec_cond = ($section_check == 'backend') ? $cfg["backend_password_recovery"] : $cfg["allow_password_recovery"];
$pass_rec_captcha = ($section_check == 'backend') ? $cfg["backend_password_recovery_captcha"] : $cfg["password_recovery_captcha"];
$user_rec_cond = ($section_check == 'backend') ? $cfg["backend_username_recovery"] : $cfg["allow_username_recovery"];
$user_rec_captcha = ($section_check == 'backend') ? $cfg["backend_username_recovery_captcha"] : $cfg["username_recovery_captcha"];
$siteKey = $cfg['recaptcha_site_key'];
$secret = $cfg['recaptcha_secret_key'];
switch (intval($_GET["t"])) {
case '':break;
case '0':break;
case '1':
if ($pass_rec_cond == 1) {
switch ($pass_rec_captcha) {
case '0':$password_recovery = (!VUserinfo::existingUsername($rec_username, $section_check)) ? $notifier->showNotice('error', $language["notif.error.invalid.request"]) : (VUserinfo::existingUsername($rec_username, $section_check)) ? VNotify::queInit('password_recovery', array(VUserinfo::getUserEmail(VUserinfo::getUserID($rec_username))), $section_check) . VNotify::showNotice('confirmation', $language["notif.success.request"], 'x_err') : null;
break;
case '1':
if (!VUserinfo::existingUsername($rec_username, $section_check) or $left_captcha == '') {
$notifier->showNotice('error', $language["notif.error.invalid.request"]);
} elseif (VUserinfo::existingUsername($rec_username, $section_check)) {
$recaptcha = new \ReCaptcha\ReCaptcha($secret, new \ReCaptcha\RequestMethod\CurlPost());
$resp = $recaptcha->verify($left_captcha, $_SERVER[REM_ADDR]);
if ($resp->isSuccess()) {
VNotify::queInit('password_recovery', array(VUserinfo::getUserEmail(VUserinfo::getUserID($rec_username))), $section_check) . VNotify::showNotice('confirmation', $language["notif.success.request"], 'x_err');
} else {
foreach ($resp->getErrorCodes() as $code) {
$notifier->showNotice('error', $code);
}
}
}
break;
}
$log = ($cfg["activity_logging"] == 1 and $action = new VActivity($user_id, 0)) ? $action->addTo('log_urecovery') : null;
}
break;
case '2':
if ($user_rec_cond == 1) {
switch ($user_rec_captcha) {
case '0':$username_recovery = (!VUserinfo::existingEmail($rec_email, $section_check)) ? $notifier->showNotice('error', $language["notif.error.invalid.request"], 'r_err') : (VUserinfo::existingEmail($rec_email, $section_check)) ? VNotify::queInit('username_recovery', array($rec_email), $section_check) . VNotify::showNotice('confirmation', $language["notif.success.request"], 'r_err') : null;
break;
case '1':
if (!VUserinfo::existingEmail($rec_email, $section_check) or $right_captcha == '') {
$notifier->showNotice('error', $language["notif.error.invalid.request"], 'r_err');
} elseif (VUserinfo::existingEmail($rec_email, $section_check)) {
$recaptcha = new \ReCaptcha\ReCaptcha($secret, new \ReCaptcha\RequestMethod\CurlPost());
$resp = $recaptcha->verify($right_captcha, $_SERVER[REM_ADDR]);
if ($resp->isSuccess()) {
VNotify::queInit('username_recovery', array($rec_email), $section_check) . VNotify::showNotice('confirmation', $language["notif.success.request"], 'r_err');
} else {
foreach ($resp->getErrorCodes() as $code) {
$notifier->showNotice('error', $code);
}
}
}
break;
}
$log = ($cfg["activity_logging"] == 1 and $action = new VActivity($user_id, 0)) ? $action->addTo('log_precovery') : null;
}
break;
default:break;
}
if (intval($_POST["reset_password"] == 1) or ($_GET["s"] != '' and $_GET["id"] != '')) {
$error_message = ($_GET["s"] != '' and $_GET["id"] != '') ? VRecovery::validCheck($section_check) : null;
$error_message = (intval($_POST["reset_password"] == 1) and $error_message == '') ? VRecovery::processForm($section_check) : $error_message;
$notice_message = ((intval($_POST["reset_password"] == 1)) and ($error_message == '' and VRecovery::doPasswordReset($section_check))) ? $language["recovery.forgot.password.confirm"] : null;
}
$u = VUserinfo::getUserInfo(VRecovery::getRecoveryID($_GET["s"]));
$recovery_username = ($_GET["s"] != '' and $_GET["id"] != '' and $section_check == 'frontend') ? $smarty->assign('fe_recovery_username', $u["uname"]) : ($_GET["s"] != '' and $_GET["id"] != '' and $section_check == 'backend') ? $smarty->assign('recovery_username', $cfg["backend_username"]) : null;
$page_display = ($_GET["t"] == '') ? $class_smarty->displayPage($section_check, ($section_check == 'frontend' ? 'tpl_recovery' : 'backend_tpl_recovery'), $error_message, $notice_message) : null;

View File

@@ -0,0 +1,8 @@
<?php
define("_ISVALID", true);
include_once "f_core/config.core.php";
// Password renewal - basic implementation
echo "<h1>Password Renewal</h1>";
echo "<p>Password renewal functionality coming soon...</p>";
echo "<a href=\"/signin\">← Back to Sign In</a>";
?>

View File

@@ -0,0 +1,85 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_recaptcha/autoload.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
$error_message = isset($_SESSION["USER_ERROR"]) ? $_SESSION["USER_ERROR"] : null;
$notice_message = null;
$cfg = $class_database->getConfigurations('login_remember,paid_memberships,frontend_signin_section,frontend_signin_count,fb_app_id,fb_app_secret,fb_auth,gp_app_id,gp_app_secret,gp_auth,recaptcha_site_key,recaptcha_secret_key,signin_captcha,list_reserved_users');
$_SESSION["renew_id"] = $cfg["paid_memberships"] == 1 ? '' : null;
if ($cfg['gp_auth'] == 1 and $cfg["frontend_signin_section"] == 1) {
//google authentication
include_once 'f_core/f_classes/class_google/Google_Client.php';
include_once 'f_core/f_classes/class_google/contrib/Google_Oauth2Service.php';
$clientId = $cfg['gp_app_id'];
$clientSecret = $cfg['gp_app_secret'];
$redirectUrl = $cfg['main_url'] . '/f_modules/m_frontend/m_auth/gp_callback_login.php';
$homeUrl = $cfg['main_url'];
$gClient = new Google_Client();
$gClient->setAccessType('online');
$gClient->setApprovalPrompt('auto');
$gClient->setClientId($clientId);
$gClient->setClientSecret($clientSecret);
$gClient->setRedirectUri($redirectUrl);
$google_oauthV2 = new Google_Oauth2Service($gClient);
$authUrl = $gClient->createAuthUrl();
$smarty->assign('gp_loginUrl', htmlspecialchars($authUrl));
}
if ($cfg['fb_auth'] == 1 and $cfg["frontend_signin_section"] == 1) {
//facebook authentication
include_once 'f_core/f_classes/class_facebook/Facebook/autoload.php';
$fb = new Facebook\Facebook([
'app_id' => $cfg['fb_app_id'],
'app_secret' => $cfg['fb_app_secret'],
'default_graph_version' => 'v2.7',
'default_access_token' => '1061711193887319|fc3a99ba0d42b98b51ac3fa124268422',
]);
$fb_helper = $fb->getRedirectLoginHelper();
$fb_permissions = ['email']; // Optional permissions
$fb_loginUrl = $fb_helper->getLoginUrl($cfg['main_url'] . '/f_modules/m_frontend/m_auth/fb_callback_login.php', $fb_permissions);
$smarty->assign('fb_loginUrl', htmlspecialchars($fb_loginUrl));
}
if (intval($_POST["frontend_global_submit"] == 1) and $cfg["frontend_signin_section"] == 1) {
//regular login
$remember = ($error_message == '' and intval($_POST["signin_remember"]) == 1) ? 1 : null;
$error_message = !VLogin::loginAttempt('frontend', $class_filter->clr_str($_POST["frontend_signin_username"]), $_POST["frontend_signin_password"], $remember) ? $language["frontend.signin.error.auth"] : null;
}
$remember = ($cfg["login_remember"] == 1 and $cfg["frontend_signin_section"] == 1) ? VLoginRemember::checkLogin('frontend') : null;
$logged_in = VLogin::isLoggedIn();
$class_smarty->displayPage('frontend', 'tpl_signin', $error_message, $notice_message);
$_SESSION["USER_ERROR"] = null;

View File

@@ -0,0 +1,25 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
$error_message = null;
$notice_message = null;
$signout = VLogin::logoutAttempt('frontend');

View File

@@ -0,0 +1,73 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class_recaptcha/autoload.php';
include_once 'f_core/f_classes/class_facebook/Facebook/autoload.php';
include_once $class_language->setLanguageFile('backend', 'language.members.entries');
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
include_once $class_language->setLanguageFile('frontend', 'language.signup');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.welcome');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('global_signup,signup_ip_access,list_ip_signup,disabled_signup_message,reserved_usernames,frontend_signin_section,frontend_signin_count,signup_min_age,signup_max_age,signup_min_password,signup_max_password,signup_min_username,signup_max_username,paid_memberships,numeric_delimiter,paypal_email,paypal_test,paypal_test_email,signup_username_availability,signup_password_meter,signup_captcha,signup_domain_restriction,list_email_domains,list_reserved_users,signup_terms,username_format,username_format_dott,username_format_dash,username_format_underscore,discount_codes,account_email_verification,account_approval,notify_welcome,paypal_payments,approve_friends,backend_notification_payment,backend_notification_signup,backend_email,backend_username,recaptcha_site_key,recaptcha_secret_key,fb_app_id,fb_app_secret,fb_auth,gp_app_id,gp_app_secret,gp_auth');
$pack_class = ($cfg["paid_memberships"] == 1 and $cfg["global_signup"] == 1 and $error_message == '') ? include_once 'f_core/f_classes/class.payment.php' : null;
$logged_in = (($_GET["do"] == 'continue' or $_GET["do"] == 'process') and intval($_SESSION["USER_ID"]) > 0 and intval($_SESSION["renew_id"]) == intval($_SESSION["USER_ID"])) ? null : VLogin::isLoggedIn();
$error_message = ($cfg["signup_ip_access"] == 1 and !VIPaccess::checkIPlist($cfg["list_ip_signup"])) ? $smarty->assign('do_disable', 'yes') : null;
$memberships = ($cfg["paid_memberships"] == 1 and $cfg["global_signup"] == 1 and $error_message == '') ? VPayment::getPackTypes() : null;
//form sessions will be reset, unless we're dealing with the second form
$formSessionReset = (intval($_POST["signup_submit_right"] != 1)) ? VSignup::formSessionReset() : null;
if (intval($_POST["frontend_global_submit"] == 1) and $cfg["global_signup"] == 1) {
//left form has been submitted
$form_fields = VArraySection::getArray('signup');
$allowedFields = $form_fields[1];
$requiredFields = $allowedFields;
$error_message = VSignup::processForm($allowedFields, $requiredFields);
$notice_message = ($error_message == '' and VSignup::formSessionInit()) ? $language["notif.notice.signup.step1"] : null;
$notice_message = ($error_message == '' and VSignup::processAccount()) ? $language["notif.notice.signup.success"] . $cfg["website_shortname"] . ($cfg["account_approval"] == 1 ? '. ' . $language["notif.notice.signup.approve"] : null) : null;
$user_info = ($cfg["paid_memberships"] == 1 and $notice_message != '') ? VPayment::getUserPack() : VUserinfo::getUserEmail();
}
if (($cfg['fb_auth'] == 1 or $cfg['gp_auth'] == 1) and $_POST and isset($_GET["do"]) and $_GET["do"] == 'auth_register') {
// $auth = VSignup::auth_register_ajax();
}
//free account update
$notice_message = ($cfg["global_signup"] == 1 and $cfg["paid_memberships"] == 1 and $_GET["p"] != '' and $_GET["u"] != '' and $_POST["signup_finalize"] == 1) ? VPayment::updateFreeEntry() : $notice_message;
//payment processing
$payment_prepare = ($cfg["global_signup"] == 1 and $cfg["paid_memberships"] == 1 and $_GET["p"] != '' and $_GET["u"] != '') ? VPayment::preparePayment() : null;
$payment_confirm = ($cfg["global_signup"] == 1 and $cfg["paid_memberships"] == 1 and $_GET["do"] == 'continue') ? VPayment::continuePayment() : null;
$payment_process = ($cfg["global_signup"] == 1 and $cfg["paid_memberships"] == 1 and ($_GET["do"] == 'process' or $_GET["do"] == 'ipn' or $_GET["do"] == 'success' or $_GET["do"] == 'cancel')) ? VPayment::doPayment($_GET["do"]) : null;
//username avail.
$username_check = (($cfg["signup_username_availability"] == 1 and $cfg["global_signup"] == 1) and ($_GET["do"] == 'ucheck' and strlen($_POST["frontend_signin_username"]) > 0)) ? VUserinfo::usernameAvailability($_POST["frontend_signin_username"]) : null;
//fb register
$fb_register = ($cfg['fb_auth'] == 1 and !isset($_GET["do"])) ? $smarty->assign('fb_register', VSignup::fb_register_button()) : null;
//gp register
$gp_register = ($cfg['gp_auth'] == 1 and !isset($_GET["do"])) ? $smarty->assign('gp_register', VSignup::gp_register_button()) : null;
//page
$display_page = !isset($_GET["do"]) ? $class_smarty->displayPage('frontend', 'tpl_signup', $error_message, $notice_message) : null;

View File

@@ -0,0 +1,35 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.signin');
include_once $class_language->setLanguageFile('frontend', 'language.notifications');
$error_message = null;
$notice_message = null;
$cfg = $class_database->getConfigurations('account_email_verification');
if ($error_message == '' and isset($_GET["sid"]) and isset($_GET["uid"])) {
$error_message = VRecovery::validCheck('frontend', 'verification');
$notice_message = ($error_message == '' and VSignup::verifyAccount()) ? $language["notif.success.verified"] : null;
} elseif ($cfg["account_email_verification"] == 0) {
$error_message = 'tpl_error_max';
}
$class_smarty->displayPage('frontend', 'tpl_verify', $error_message, $notice_message);

View File

@@ -0,0 +1,31 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
if (empty($_SERVER["argv"][1])) {
exit;
}
include_once 'f_core/config.core.php';
$dash = new VbeDashboard();
$dash->updateDashboardStats();

View File

@@ -0,0 +1,27 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$dash = new VbeDashboard();
$dash->updateNotifications();

View File

@@ -0,0 +1,27 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class.conversion.php';
$conv = new VQue();
$conv->cache_clear();

View File

@@ -0,0 +1,14 @@
<?php
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
/* database */
$dbhost = getenv('DB_HOST') ?: 'db';
$dbname = getenv('DB_NAME') ?: 'easystream';
$dbuser = getenv('DB_USER') ?: 'easystream';
$dbpass = getenv('DB_PASS') ?: 'easystream';
/* main url */
$base = getenv('CRON_BASE_URL') ?: 'http://localhost:8080';
/* cron salt key */
$ssk = getenv('CRON_SSK') ?: 'CHANGE_ME_IN_BACKEND';

View File

@@ -0,0 +1,139 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../../');
set_include_path($main_dir);
include_once 'filter.php';
$class_filter = new VFilter;
$host = array('127.0.0.1');
$_POST = $HTTP_RAW_POST_DATA = file_get_contents('php://input');
//$_POST = $HTTP_RAW_POST_DATA;
$post = json_decode($_POST);
if ($_POST and in_array($_SERVER["REMOTE_ADDR"], $host)) {
require 'cfg.php';
$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
if (!$conn) {
die('Could not connect: ' . mysqli_error());
}
echo "Connected successfully\n";
$salt = $cfg["live_chat_salt"];
$p_chatid = $class_filter->clr_str($post->a);
$p_fkey = $class_filter->clr_str($post->b);
$p_nick = $class_filter->clr_str($post->c);
$p_dnick = $class_filter->clr_str($post->cd);
$p_ip = $class_filter->clr_str($post->d);
$p_chid = $class_filter->clr_str($post->e);
$p_uid = $class_filter->clr_str($post->f);
$p_cua = $class_filter->clr_str($post->g);
$p_own = $class_filter->clr_str($post->h);
$p_ukey = $class_filter->clr_str($post->i);
$p_badge = $class_filter->clr_str($post->j);
$p_live = (int) $post->k;
$p_first = (int) $post->first;
$p_inc = (int) $post->inc;
$cip = $p_ip;
$p_fp = $p_cua;
$q = sprintf("SELECT `db_id` FROM `db_livechat` WHERE `channel_id`='%s' AND `chat_id`='%s' AND `stream_id`='%s' LIMIT 1;", $p_chid, $p_chatid, $p_fkey);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
$v = $r->fetch_assoc();
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livechat` (`first`, `chat_id`, `channel_id`, `channel_owner`, `usr_id`, `usr_key`, `stream_id`, `chat_user`, `chat_display`, `is_live`, `chat_ip`, `chat_fp`, `chat_time`, `badge`, `logged_in`, `usr_profileinc`) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');",
$p_first, $p_chatid, $p_chid, $p_own, $p_uid, $p_ukey, $p_fkey, $p_nick, $p_dnick, $p_live, $p_ip, $p_fp, date("Y-m-d H:i:s"), $p_badge, (substr($p_nick, 0, 5) === "Guest" ? 0 : 1), $p_inc);
$r = mysqli_query($conn, $q);
} else {
$q = sprintf("UPDATE `db_livechat` SET `is_live`='%s', `chat_display`='%s', `usr_profileinc`='%s', `first`='%s', `chat_ip`='%s', `chat_fp`='%s' WHERE `db_id`='%s' LIMIT 1;", $p_live, $p_dnick, $p_inc, $p_first, $p_ip, $p_fp, $v["db_id"]);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_livemods` WHERE `channel_id`='%s' LIMIT 1;", $p_chid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livemods` (`channel_id`, `mod_list`) VALUES ('%s', '[]');", $p_chid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_livevips` WHERE `channel_id`='%s' LIMIT 1;", $p_chid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livevips` (`channel_id`, `vip_list`) VALUES ('%s', '[]');", $p_chid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_livebans` WHERE `channel_id`='%s' LIMIT 1;", $p_chid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livebans` (`channel_id`, `ban_list`) VALUES ('%s', '[]');", $p_chid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_livefollows` WHERE `channel_id`='%s' LIMIT 1;", $p_chid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livefollows` (`channel_id`, `follow_list`) VALUES ('%s', '[]');", $p_chid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_livesubs` WHERE `channel_id`='%s' LIMIT 1;", $p_chid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livesubs` (`channel_id`, `sub_list`) VALUES ('%s', '[]');", $p_chid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_livesettings` WHERE `channel_id`='%s' LIMIT 1;", $p_chid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livesettings` (`channel_id`) VALUES ('%s');", $p_chid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `db_id` FROM `db_liveignore` WHERE `usr_id`='%s' LIMIT 1;", $p_uid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_liveignore` (`usr_id`, `ignore_list`) VALUES ('%s', '[]');", $p_uid);
$r = mysqli_query($conn, $q);
}
$q = sprintf("SELECT `color_class`, `color_code`, `timestamps`, `modicons` FROM `db_livecolors` WHERE `usr_id`='%s' LIMIT 1", $p_uid);
$r = mysqli_query($conn, $q);
$rn = $r->num_rows;
if ($rn == 0) {
$q = sprintf("INSERT INTO `db_livecolors` (`usr_id`, `color_class`, `modicons`, `timestamps`) VALUES ('%s', '%s', '0', '0');", $p_uid, 'c' . rand(1, 15));
$r = mysqli_query($conn, $q);
}
mysqli_close($conn);
}

View File

@@ -0,0 +1,44 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
require 'cfg.php';
$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
if (!$conn) {
die('Could not connect: ' . mysqli_error());
}
echo "Connected successfully\n";
$sql = sprintf("DELETE FROM `db_livenotifications` WHERE `displayed`='1';");
if (mysqli_query($conn, $sql)) {
echo "db_livenotifications updated successfully\n";
} else {
echo "Error updating table db_livenotifications: " . mysqli_error($conn) . "\n";
}
$sql = "DELETE FROM `db_livechat` WHERE `chat_user` LIKE 'Guest%';";
if (mysqli_query($conn, $sql)) {
echo "db_livechat records updated successfully\n";
} else {
echo "Error updating db_livechat: " . mysqli_error($conn) . "\n";
}
mysqli_close($conn);

View File

@@ -0,0 +1,341 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
defined('_ISVALID') or header('Location: /error');
/**
* Sanitize HTML body content
* Remove dangerous tags and attributes that can lead to security issues like
* XSS or HTTP response splitting
*/
class VFilter
{
// Private fields
public $_encoding;
public $_allowedTags;
public $_allowJavascriptEvents;
public $_allowJavascriptInUrls;
public $_allowObjects;
public $_allowScript;
public $_allowStyle;
public $_additionalTags;
/**
* Constructor
*/
public function HTML_Sanitizer()
{
$this->resetAll();
}
/**
* (re)set all options to default value
*/
public function resetAll()
{
$this->_encoding = 'UTF-8';
$this->_allowDOMEvents = false;
$this->_allowJavascriptInUrls = false;
$this->_allowStyle = false;
$this->_allowScript = false;
$this->_allowObjects = false;
$this->_allowStyle = false;
$this->_allowedTags = '<a><br><b><h1><h2><h3><h4><h5><h6>'
. '<img><li><ol><p><strong><table><tr><td><th><u><ul><thead>'
. '<tbody><tfoot><em><dd><dt><dl><span><div><del><add><i><hr>'
. '<pre><br><blockquote><address><code><caption><abbr><acronym>'
. '<cite><dfn><q><ins><sup><sub><kbd><samp><var><tt><small><big>'
;
$this->_additionalTags = '';
}
/**
* Add additional tags to allowed tags
* @param string
* @access public
*/
public function addAdditionalTags($tags)
{$this->_additionalTags .= $tags;}
/**
* Allow object, embed, applet and param tags in html
* @access public
*/
public function allowObjects()
{$this->_allowObjects = true;}
/**
* Allow DOM event on DOM elements
* @access public
*/
public function allowDOMEvents()
{$this->_allowDOMEvents = true;}
/**
* Allow script tags
* @access public
*/
public function allowScript()
{$this->_allowScript = true;}
/**
* Allow the use of javascript: in urls
* @access public
*/
public function allowJavascriptInUrls()
{$this->_allowJavascriptInUrls = true;}
/**
* Allow style tags and attributes
* @access public
*/
public function allowStyle()
{$this->_allowStyle = true;}
/**
* Helper to allow all javascript related tags and attributes
* @access public
*/
public function allowAllJavascript()
{
$this->allowDOMEvents();
$this->allowScript();
$this->allowJavascriptInUrls();
}
/**
* Allow all tags and attributes
* @access public
*/
public function allowAll()
{
$this->allowAllJavascript();
$this->allowObjects();
$this->allowStyle();
}
/**
* Filter URLs to avoid HTTP response splitting attacks
* @access public
* @param string url
* @return string filtered url
*/
public function filterHTTPResponseSplitting($url)
{
$dangerousCharactersPattern = '~(\r\n|\r|\n|%0a|%0d|%0D|%0A)~';
return preg_replace($dangerousCharactersPattern, '', $url);
}
/**
* Remove potential javascript in urls
* @access public
* @param string url
* @return string filtered url
*/
public function removeJavascriptURL($str)
{
$HTML_Sanitizer_stripJavascriptURL = 'javascript:[^"]+';
$str = preg_replace("/$HTML_Sanitizer_stripJavascriptURL/i"
, ''
, $str);
return $str;
}
/**
* Remove potential flaws in urls
* @access private
* @param string url
* @return string filtered url
*/
public function sanitizeURL($url)
{
if (!$this->_allowJavascriptInUrls) {$url = $this->removeJavascriptURL($url);}
$url = $this->filterHTTPResponseSplitting($url);
return $url;
}
/**
* Callback for PCRE
* @access private
* @param matches array
* @return string
* @see sanitizeURL
*/
public function _sanitizeURLCallback($matches)
{return 'href="' . $this->sanitizeURL($matches[1]) . '"';}
/**
* Remove potential flaws in href attributes
* @access private
* @param string html tag
* @return string filtered html tag
*/
public function sanitizeHref($str)
{
$HTML_Sanitizer_URL = 'href="([^"]+)"';
return preg_replace_callback("/$HTML_Sanitizer_URL/i"
, array(&$this, '_sanitizeURLCallback')
, $str);
}
/**
* Callback for PCRE
* @access private
* @param matches array
* @return string
* @see sanitizeURL
*/
public function _sanitizeSrcCallback($matches)
{return 'src="' . $this->sanitizeURL($matches[1]) . '"';}
/**
* Remove potential flaws in href attributes
* @access private
* @param string html tag
* @return string filtered html tag
*/
public function sanitizeSrc($str)
{
$HTML_Sanitizer_URL = 'src="([^"]+)"';
return preg_replace_callback("/$HTML_Sanitizer_URL/i"
, array(&$this, '_sanitizeSrcCallback')
, $str);
}
/**
* Remove dangerous attributes from html tags
* @access private
* @param string html tag
* @return string filtered html tag
*/
public function removeEvilAttributes($str)
{
if (!$this->_allowDOMEvents) {
$str = preg_replace_callback('/<(.*?)>/i'
, array(&$this, '_removeDOMEventsCallback')
, $str);
}
if (!$this->_allowStyle) {
$str = preg_replace_callback('/<(.*?)>/i'
, array(&$this, '_removeStyleCallback')
, $str);
}
return $str;
}
/**
* Remove DOM events attributes from html tags
* @access private
* @param string html tag
* @return string filtered html tag
*/
public function removeDOMEvents($str)
{
$str = preg_replace('/\s*=\s*/', '=', $str);
$HTML_Sanitizer_stripAttrib = '(onclick|ondblclick|onmousedown|'
. 'onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|'
. 'onkeyup|onfocus|onblur|onabort|onerror|onload)'
;
$str = stripslashes(preg_replace("/$HTML_Sanitizer_stripAttrib/i"
, 'forbidden'
, $str));
return $str;
}
/**
* Callback for PCRE
* @access private
* @param matches array
* @return string
* @see removeDOMEvents
*/
public function _removeDOMEventsCallback($matches)
{return '<' . $this->removeDOMEvents($matches[1]) . '>';}
/**
* Remove style attributes from html tags
* @access private
* @param string html tag
* @return string filtered html tag
*/
public function removeStyle($str)
{
$str = preg_replace('/\s*=\s*/', '=', $str);
$HTML_Sanitizer_stripAttrib = '(style)'
;
$str = stripslashes(preg_replace("/$HTML_Sanitizer_stripAttrib/i"
, 'forbidden'
, $str));
return $str;
}
/**
* Callback for PCRE
* @access private
* @param matches array
* @return string
* @see removeStyle
*/
public function _removeStyleCallback($matches)
{return '<' . $this->removeStyle($matches[1]) . '>';}
/**
* Remove dangerous HTML tags
* @access private
* @param string html code
* @return string filtered url
*/
public function removeEvilTags($str)
{
$allowedTags = $this->_allowedTags;
if ($this->_allowScript) {$allowedTags .= '<script>';}
if ($this->_allowStyle) {$allowedTags .= '<style>';}
if ($this->_allowObjects) {$allowedTags .= '<object><embed><applet><param>';}
$allowedTags .= $this->_additionalTags;
$str = strip_tags($str, $allowedTags);
return $str;
}
public function removeSQLTags($str)
{
$str = str_ireplace(array('CONCAT', 'ELT(', 'INFORMATION_SCHEMA'), array('', '', ''), $str);
return $str;
}
/**
* Sanitize HTML
* remove dangerous tags and attributes
* clean urls
* @access public
* @param string html code
* @return string sanitized html code
*/
public function sanitize($html)
{
$html = $this->removeEvilTags($html);
$html = $this->removeEvilAttributes($html);
$html = $this->sanitizeHref($html);
$html = $this->sanitizeSrc($html);
$html = $this->removeSQLTags($html);
return $html;
}
public function clr_str($str)
{
static $san = null;
if (empty($san)) {$san = new VFilter;}
return htmlspecialchars($san->sanitize($str), ENT_QUOTES, $this->_encoding);
}
}
function html_sanitize($str)
{
static $san = null;
if (empty($san)) {$san = new VFilter;}
return $san->sanitize($str);
}

View File

@@ -0,0 +1,99 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../../');
set_include_path($main_dir);
include_once 'filter.php';
$class_filter = new VFilter;
//include_once $class_language->setLanguageFile('frontend', 'language.global');
$host = array('127.0.0.1');
$_POST = $HTTP_RAW_POST_DATA = file_get_contents('php://input');
$post = json_decode($_POST);
if ($_POST and in_array($_SERVER["REMOTE_ADDR"], $host)) {
require 'cfg.php';
$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
if (!$conn) {
die('Could not connect: ' . mysqli_error($conn));
}
echo "Connected successfully\n";
$cid = $class_filter->clr_str($post->a);
$sid = $class_filter->clr_str($post->e);
$type = is_array($post->b) ? $post->b : $class_filter->clr_str($post->b);
$user = $class_filter->clr_str($post->c);
$user2 = $class_filter->clr_str($post->d);
$pk = $class_filter->clr_str($post->g);
switch ($type) {
case "follow":
case "unfollow":
$text = $type == 'follow' ? $user . ' is now following' : $user . ' has unfollowed';
$sql = sprintf("SELECT `db_id`, `chat_user`, `channel_id`, `channel_owner`, `usr_id`, `logged_in` FROM `db_livechat` WHERE `stream_id`='%s' AND `chat_id`='%s' ORDER BY `db_id` DESC LIMIT 1;", $sid, $cid);
$r = mysqli_query($conn, $sql);
$rn = $r->num_rows;
$rv = $r->fetch_assoc();
if ($rn > 0) {
$ch_id = $rv["channel_id"];
$sql = sprintf("SELECT `db_id` FROM `db_livenotifications` WHERE `type`='follow' AND `channel_id`='%s' AND `text` LIKE '%s' LIMIT 1;", $ch_id, $user . '%');
$rr = mysqli_query($conn, $sql);
if ($rr->num_rows == 0 and $type == 'follow') {
$q = sprintf("INSERT INTO `db_livenotifications` (`type`, `channel_id`, `text`, `displayed`) VALUES ('%s', '%s', '%s', '0');", $type, $ch_id, $text);
mysqli_query($conn, $q);
}
}
mysqli_close($conn);
break;
default:
if (is_array($type) and ($type[0] == 'subscribe' or $type[0] == 'unsubscribe')) {
$text = $type[0] == 'subscribe' ? $user2 . ' has subscribed with a ' . $pk . ' subscription' : $user2 . ' has unsubscribed';
$sql = sprintf("SELECT `db_id`, `chat_user`, `channel_id`, `channel_owner`, `usr_id`, `logged_in` FROM `db_livechat` WHERE `stream_id`='%s' AND `chat_id`='%s' ORDER BY `db_id` DESC LIMIT 1;", $sid, $cid);
$r = mysqli_query($conn, $sql);
$rn = $r->num_rows;
$rv = $r->fetch_assoc();
if ($rn > 0) {
$ch_id = $rv["channel_id"];
if ($type[0] == 'subscribe') {
$q = sprintf("INSERT INTO `db_livenotifications` (`type`, `channel_id`, `text`, `displayed`) VALUES ('%s', '%s', '%s', '0');", $type[0], $ch_id, $text);
mysqli_query($conn, $q);
}
}
}
mysqli_close($conn);
break;
}
}
if ($conn) {
mysqli_close($conn);
}

View File

@@ -0,0 +1,102 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
require 'cfg.php';
$url = $base . '/syncsubs?s=';
$date = date("Y-m-d");
$tk = md5($date . $ssk);
$url .= $tk;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
$data = curl_exec($curl);
curl_close($curl);
$list = json_decode($data);
/* follows */
$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
if (!$conn) {
die('Could not connect: ' . mysqli_error());
}
echo "Connected successfully\n";
if (is_object($list->followers)) {
foreach ($list->followers as $usr_id => $users) {
if ($usr_id > 0) {
$sql = sprintf("SELECT `db_id` FROM `db_livefollows` WHERE `channel_id`='%s' LIMIT 1;", $usr_id);
$r = mysqli_query($conn, $sql);
$n = $r->num_rows;
$v = $r->fetch_assoc();
if ($n > 0) {
$sql = sprintf("UPDATE `db_livefollows` SET `follow_list`='%s' WHERE `db_id`='%s' LIMIT 1;", json_encode($users), $v["db_id"]);
} else {
$sql = sprintf("INSERT INTO `db_livefollows` (`channel_id`, `follow_list`) VALUES ('%s', '%s');", $usr_id, json_encode($users));
}
if (mysqli_query($conn, $sql)) {
echo "db_livefollows records updated successfully for channel_id $usr_id\n";
} else {
echo "Error updating record: " . mysqli_error($conn) . "\n";
}
}
}
}
mysqli_close($conn);
/* subs */
$conn = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname);
if (!$conn) {
die('Could not connect: ' . mysqli_error());
}
echo "Connected successfully\n";
if (is_object($list->subscribers)) {
foreach ($list->subscribers as $usr_id => $users) {
if ($usr_id > 0) {
$sql = sprintf("SELECT `db_id` FROM `db_livesubs` WHERE `channel_id`='%s' LIMIT 1;", $usr_id);
$r = mysqli_query($conn, $sql);
$n = $r->num_rows;
$v = $r->fetch_assoc();
if ($n > 0) {
$sql = sprintf("UPDATE `db_livesubs` SET `sub_list`='%s' WHERE `db_id`='%s' LIMIT 1;", json_encode($users), $v["db_id"]);
} else {
$sql = sprintf("INSERT INTO `db_livesubs` (`channel_id`, `sub_list`) VALUES ('%s', '%s');", $usr_id, json_encode($users));
}
if (mysqli_query($conn, $sql)) {
echo "db_livesubs records updated successfully for channel_id $usr_id\n";
} else {
echo "Error updating record: " . mysqli_error($conn) . "\n";
}
}
}
}
mysqli_close($conn);

View File

@@ -0,0 +1,32 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$sql = "DELETE FROM `db_livechat` WHERE `chat_user` LIKE 'Guest%';";
$rs = $db->execute($sql);
if ($db->Affected_Rows() > 0) {
echo "db_livechat records updated successfully\n";
} else {
echo "db_livechat not updated\n";
}

View File

@@ -0,0 +1,32 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$sql = sprintf("DELETE FROM `db_livetemps` WHERE DATE(`date`) < DATE(NOW() - INTERVAL 2 DAY);");
$rs = $db->execute($sql);
if ($db->Affected_Rows() > 0) {
echo "db_livetemps records updated successfully\n";
} else {
echo "db_livetemps not updated\n";
}

View File

@@ -0,0 +1,62 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class.conversion.php';
$cfg[] = $class_database->getConfigurations('conversion_video_que,conversion_short_que,conversion_image_que,conversion_audio_que,conversion_document_que');
$conv = new VQue();
if ($cfg["video_module"] == 1 and $cfg["conversion_video_que"] == 1 and $conv->load("video")) {
if ($conv->check()) {
$conv->startConversion();
echo "\n";
}
}
if ($cfg["short_module"] == 1 and $cfg["conversion_short_que"] == 1 and $conv->load("short")) {
if ($conv->check()) {
$conv->startConversion();
echo "\n";
}
}
if ($cfg["image_module"] == 1 and $cfg["conversion_image_que"] == 1 and $conv->load("image")) {
if ($conv->check()) {
$conv->startConversion();
echo "\n";
}
}
if ($cfg["audio_module"] == 1 and $cfg["conversion_audio_que"] == 1 and $conv->load("audio")) {
if ($conv->check()) {
$conv->startConversion();
echo "\n";
}
}
if ($cfg["document_module"] == 1 and $cfg["conversion_document_que"] == 1 and $conv->load("doc")) {
if ($conv->check()) {
$conv->startConversion();
echo "\n";
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_time_limit(0);
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$cfg[] = $class_database->getConfigurations('backend_email');
$bytes = disk_free_space("/");
$si_prefix = array('B', 'KB', 'MB', 'GB', 'TB', 'EB', 'ZB', 'YB');
$base = 1024;
$class = min((int) log($bytes, $base), count($si_prefix) - 1);
$data = sprintf('%1.2f', $bytes / pow($base, $class)) . ' ' . $si_prefix[$class];
$free = sprintf('%1.2f', $bytes / pow($base, $class));
if ($si_prefix[$class] == 'GB') {
if ($free < 5) {
VNotify::queInit('disk_usage', array($cfg['backend_email']), $data);
}
}
if ($si_prefix[$class] == 'MB') {
if ($free < 900) {
VNotify::queInit('disk_usage', array($cfg['backend_email']), $data);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$act = VSubscriber::check_expired_subs();

View File

@@ -0,0 +1,30 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_time_limit(0);
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$mail_type = $class_filter->clr_str($_SERVER["argv"][1]);
$mail_key = $class_filter->clr_str($_SERVER["argv"][2]);
$do_notify = VNotify::Mailer($mail_type, $mail_key);

View File

@@ -0,0 +1,66 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_time_limit(0);
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once $class_language->setLanguageFile('frontend', 'language.global');
include_once $class_language->setLanguageFile('frontend', 'language.email.notif');
$period = $class_filter->clr_str($_SERVER["argv"][1]);
$db_period = $period == 'daily' ? 1 : ($period == 'weekly' ? 2 : 0);
if ($db_period > 0) {
$sql = sprintf("SELECT `usr_id`, `usr_user`, `usr_email` FROM `db_accountuser` WHERE `usr_weekupdates`='%s' AND `usr_status`='1';", $db_period);
$res = $db->execute($sql);
if ($res->fields["usr_id"]) {
$_i = array();
while (!$res->EOF) {
$n = 0;
$sql = sprintf("SELECT `usr_id` FROM `db_subscribers` WHERE `sub_id`='%s';", $res->fields["usr_id"]);
$r = $db->execute($sql);
if ($r->fields["usr_id"]) {
while (!$r->EOF) {
$_user1 = $res->fields["usr_id"];
$_user2 = $r->fields["usr_id"];
$_i[$_user1][$n] = $_user2;
$r->MoveNext();
$n++;
}
}
$res->MoveNext();
}
if (count($_i) > 0) {
foreach ($_i as $u => $v) {
$_mail = VUserinfo::getUserEmail($u);
VNotify::queInit('email_digest', array($_mail), array(VUserinfo::getUserName($u), $v, $db_period));
}
}
}
}

View File

@@ -0,0 +1,503 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_time_limit(0);
//ignore_user_abort(1);
set_include_path($main_dir);
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
include_once 'f_core/config.core.php';
if (empty($_SERVER["argv"][1])) {
exit;
}
$sitemap_basename = 'basename';
$limit = null;
$vidid = null;
$p = null;
$mp4 = null;
$type = $class_filter->clr_str($_SERVER["argv"][1]);
if ($type != 'video' and $type != 'short' and $type != 'image') {
exit;
}
if (isset($_GET['limit'])) {
$limit = preg_replace("/[^0-9,]/", "", $_GET['limit']);
if ($limit) {
$limit = "LIMIT $limit";
}
}
if (isset($_GET['vid'])) {
$vidid = preg_replace("/[^0-9]/", "", $_GET['vid']);
if ($vidid) {
$vidid = "AND vid_id > $vidid";
}
}
$skey = "last_" . $type . "_sitemap";
$cfg[] = $class_database->getConfigurations($skey . ',stream_method,stream_server,stream_lighttpd_secure');
$lvid = $cfg[$skey];
//$lvid = 0;//reset, start from id0
$vidid = "AND A.`db_id` > $lvid";
$limit = 'LIMIT 1000';
$sql = sprintf("SELECT
A.`db_id`, A.`file_key`, A.`upload_date`, A.`file_duration`, A.`file_views`, A.`upload_server`, A.`thumb_server`, A.`embed_src`, A.`embed_key`, A.`file_hd`, A.`thumb_cache`,
A.`file_title`, A.`file_description`, A.`file_tags`,
D.`usr_key`
FROM `db_%sfiles` A, `db_accountuser` D WHERE
A.`usr_id`=D.`usr_id` AND
A.`active`='1' AND A.`deleted`='0' AND A.`approved`='1'
%s ORDER BY A.`db_id` ASC %s;", $type, $vidid, $limit);
$res = $db->execute($sql);
//exit;
$SitemapNode = new VSitemap($cfg['sitemap_dir'] . '/sm_' . $type, $sitemap_basename, $type);
if ($res->fields["file_key"]) {
while (!$res->EOF) {
$db_id = $res->fields["db_id"];
$vid_id = $res->fields["file_key"];
$usr_id = $res->fields["usr_key"];
$tmb_srv = $res->fields["thumb_server"];
$vid_srv = $res->fields["upload_server"];
$db_title = $res->fields["file_title"];
$_fsrc = $res->fields["embed_src"];
$_furl = $res->fields["embed_key"];
$hd = $res->fields["file_hd"];
$thumb_cache = $res->fields["thumb_cache"];
$thumb_cache = $thumb_cache > 1 ? $thumb_cache : null;
if ($hd == 0) {
$hd = '';
}
$title = html_convert_entities(htmlspecialchars($db_title));
$description = html_convert_entities(htmlspecialchars($res->fields["file_description"]));
$screenshot = VGenerate::thumbSigned($type, $vid_id, array($usr_id, $thumb_cache), (3600 * 24 * 7), 0);
$duration = $res->fields["file_duration"];
$view_count = $res->fields["file_views"];
$tags = explode(" ", $res->fields["file_tags"]);
$date = date(DATE_ATOM, strtotime($res->fields["upload_date"]));
$url = VGenerate::fileURL($type, $vid_id, 'upload');
$link = $cfg["main_url"] . '/' . VGenerate::fileHref($type[0], $vid_id, $db_title);
$_src = VPlayers::fileSources($type, $usr_id, $vid_id);
$sql = sprintf("SELECT
A.`server_type`, A.`lighttpd_url`, A.`lighttpd_secdownload`, A.`lighttpd_prefix`, A.`lighttpd_key`, A.`cf_enabled`, A.`cf_dist_type`,
A.`cf_signed_url`, A.`cf_signed_expire`, A.`cf_key_pair`, A.`cf_key_file`, A.`s3_bucketname`, A.`s3_accesskey`, A.`s3_secretkey`,
B.`upload_server`
FROM
`db_servers` A, `db_%sfiles` B
WHERE
B.`file_key`='%s' AND
B.`upload_server` > '0' AND
A.`server_id`=B.`upload_server` LIMIT 1;", $type, $vid_id);
$srv = $db->execute($sql);
$cf_signed_url = $srv->fields["cf_signed_url"];
$cf_signed_expire = 3600 * 24 * 2; //$srv->fields["cf_signed_expire"];
$cf_key_pair = $srv->fields["cf_key_pair"];
$cf_key_file = $srv->fields["cf_key_file"];
$aws_access_key_id = $srv->fields["s3_accesskey"];
$aws_secret_key = $srv->fields["s3_secretkey"];
$aws_bucket = $srv->fields["s3_bucketname"];
if (count($_src) > 0) {
$_file = array();
foreach ($_src as $k => $v) {
if (($srv->fields["lighttpd_url"] != '' and $srv->fields["lighttpd_secdownload"] == 1) or ($cfg["stream_method"] == 2 and $cfg["stream_server"] == 'lighttpd' and $cfg["stream_lighttpd_secure"] == 1)) {
$l0 = explode("/", $v[0]);
$loc0 = $cfg["media_files_dir"] . '/' . $l0[6] . '/' . $l0[7] . '/' . $l0[8];
$l1 = explode("/", $v[1]);
$loc1 = $cfg["media_files_dir"] . '/' . $l1[6] . '/' . $l1[7] . '/' . $l1[8];
$l2 = explode("/", $v[2]);
$loc2 = $cfg["media_files_dir"] . '/' . $l2[6] . '/' . $l2[7] . '/' . $l2[8];
} elseif ($cfg["stream_method"] == 2 and $cfg["stream_server"] == 'lighttpd' and $cfg["stream_lighttpd_secure"] == 0) {
$loc0 = str_replace($cfg["stream_lighttpd_url"], $cfg["media_files_dir"], $v[0]);
$loc1 = str_replace($cfg["stream_lighttpd_url"], $cfg["media_files_dir"], $v[1]);
$loc2 = str_replace($cfg["stream_lighttpd_url"], $cfg["media_files_dir"], $v[2]);
} else {
$loc0 = str_replace($url, $cfg["media_files_dir"], $v[0]);
$loc1 = str_replace($url, $cfg["media_files_dir"], $v[1]);
$loc2 = str_replace($url, $cfg["media_files_dir"], $v[2]);
if ($srv->fields["server_type"] == 's3' and $srv->fields["cf_enabled"] == 1 and $cf_signed_url == 1) {
if ($srv->fields["cf_dist_type"] == 'r') {
$v[0] = strstr($v[0], $videos->fields["usr_key"]);
$v[1] = strstr($v[1], $videos->fields["usr_key"]);
$v[2] = strstr($v[2], $videos->fields["usr_key"]);
$v[0] = VbeServers::getS3SignedURL($aws_access_key_id, $aws_secret_key, $v[0], $aws_bucket, $cf_signed_expire);
$v[1] = VbeServers::getS3SignedURL($aws_access_key_id, $aws_secret_key, $v[1], $aws_bucket, $cf_signed_expire);
$v[2] = VbeServers::getS3SignedURL($aws_access_key_id, $aws_secret_key, $v[2], $aws_bucket, $cf_signed_expire);
} else {
$v[0] = VbeServers::getSignedURL($v[0], $cf_signed_expire, $cf_key_pair, $cf_key_file, 0, 0);
$v[1] = VbeServers::getSignedURL($v[1], $cf_signed_expire, $cf_key_pair, $cf_key_file, 0, 0);
$v[2] = VbeServers::getSignedURL($v[2], $cf_signed_expire, $cf_key_pair, $cf_key_file, 0, 0);
}
}
}
if ($hd == '') {
if (file_exists($loc0)) {
$_file[] = $v[0];
} else {
if (file_exists($loc1)) {
$_file[] = $v[1];
}
}
} else {
if (file_exists($loc0)) {
$_file[] = $v[0];
} else {
if (file_exists($loc1)) {
$_file[] = $v[1];
}
}
}
}
}
if ($_fsrc != 'local') {
$info = array();
$_p = VPlayers::playerInit('view');
$width = $_p[1];
$height = $_p[2];
$info["key"] = $vid_id;
switch ($_fsrc) {
case "youtube":$_url = 'https://www.youtube.com/embed/' . $_furl;
break;
case "vimeo":$_url = 'https://player.vimeo.com/video/' . $_furl;
break;
case "dailymotion":$_url = 'https://www.dailymotion.com/embed/video/' . $_furl;
break;
}
}
$config = array(
'type' => 'url',
'params' => array(
'loc' => $link,
'video' => array(
'title' => $title,
'thumbnail_loc' => $screenshot,
'description' => str_replace(array("\r", "\n"), array("", ""), $description),
($_fsrc != 'local' ? 'player_loc' : 'content_loc') => ($_fsrc != 'local' ? $_url : ($hd == '' ? $_file[0] : ($_file[2] != '' ? $_file[2] : $_file[1]))),
'publication_date' => $date,
'duration' => $duration,
'view_count' => $view_count,
),
),
);
foreach ($tags as $i => $tag) {
$final_tag = trim(html_convert_entities(htmlspecialchars($tag)));
$config['params']['video']['tag' . $i] = str_replace(array("\r", "\n"), array("", ""), $final_tag);
}
$SitemapNode->add_node($config);
$db->execute(sprintf("UPDATE `db_settings` SET `cfg_data`='%s' WHERE `cfg_name`='%s' LIMIT 1;", $db_id, $skey));
$res->MoveNext();
} //endwhile
} //endif
function html_convert_entities($string)
{
return preg_replace_callback('/&([a-zA-Z][a-zA-Z0-9]+);/',
'convert_entity', $string);
}
/* Swap HTML named entity with its numeric equivalent. If the entity
* isn't in the lookup table, this function returns a blank, which
* destroys the character in the output - this is probably the
* desired behaviour when producing XML. */
function convert_entity($matches)
{
static $table = array('quot' => '&#34;',
'amp' => '&#38;',
'lt' => '&#60;',
'gt' => '&#62;',
'OElig' => '&#338;',
'oelig' => '&#339;',
'Scaron' => '&#352;',
'scaron' => '&#353;',
'Yuml' => '&#376;',
'circ' => '&#710;',
'tilde' => '&#732;',
'ensp' => '&#8194;',
'emsp' => '&#8195;',
'thinsp' => '&#8201;',
'zwnj' => '&#8204;',
'zwj' => '&#8205;',
'lrm' => '&#8206;',
'rlm' => '&#8207;',
'ndash' => '&#8211;',
'mdash' => '&#8212;',
'lsquo' => '&#8216;',
'rsquo' => '&#8217;',
'sbquo' => '&#8218;',
'ldquo' => '&#8220;',
'rdquo' => '&#8221;',
'bdquo' => '&#8222;',
'dagger' => '&#8224;',
'Dagger' => '&#8225;',
'permil' => '&#8240;',
'lsaquo' => '&#8249;',
'rsaquo' => '&#8250;',
'euro' => '&#8364;',
'fnof' => '&#402;',
'Alpha' => '&#913;',
'Beta' => '&#914;',
'Gamma' => '&#915;',
'Delta' => '&#916;',
'Epsilon' => '&#917;',
'Zeta' => '&#918;',
'Eta' => '&#919;',
'Theta' => '&#920;',
'Iota' => '&#921;',
'Kappa' => '&#922;',
'Lambda' => '&#923;',
'Mu' => '&#924;',
'Nu' => '&#925;',
'Xi' => '&#926;',
'Omicron' => '&#927;',
'Pi' => '&#928;',
'Rho' => '&#929;',
'Sigma' => '&#931;',
'Tau' => '&#932;',
'Upsilon' => '&#933;',
'Phi' => '&#934;',
'Chi' => '&#935;',
'Psi' => '&#936;',
'Omega' => '&#937;',
'alpha' => '&#945;',
'beta' => '&#946;',
'gamma' => '&#947;',
'delta' => '&#948;',
'epsilon' => '&#949;',
'zeta' => '&#950;',
'eta' => '&#951;',
'theta' => '&#952;',
'iota' => '&#953;',
'kappa' => '&#954;',
'lambda' => '&#955;',
'mu' => '&#956;',
'nu' => '&#957;',
'xi' => '&#958;',
'omicron' => '&#959;',
'pi' => '&#960;',
'rho' => '&#961;',
'sigmaf' => '&#962;',
'sigma' => '&#963;',
'tau' => '&#964;',
'upsilon' => '&#965;',
'phi' => '&#966;',
'chi' => '&#967;',
'psi' => '&#968;',
'omega' => '&#969;',
'thetasym' => '&#977;',
'upsih' => '&#978;',
'piv' => '&#982;',
'bull' => '&#8226;',
'hellip' => '&#8230;',
'prime' => '&#8242;',
'Prime' => '&#8243;',
'oline' => '&#8254;',
'frasl' => '&#8260;',
'weierp' => '&#8472;',
'image' => '&#8465;',
'real' => '&#8476;',
'trade' => '&#8482;',
'alefsym' => '&#8501;',
'larr' => '&#8592;',
'uarr' => '&#8593;',
'rarr' => '&#8594;',
'darr' => '&#8595;',
'harr' => '&#8596;',
'crarr' => '&#8629;',
'lArr' => '&#8656;',
'uArr' => '&#8657;',
'rArr' => '&#8658;',
'dArr' => '&#8659;',
'hArr' => '&#8660;',
'forall' => '&#8704;',
'part' => '&#8706;',
'exist' => '&#8707;',
'empty' => '&#8709;',
'nabla' => '&#8711;',
'isin' => '&#8712;',
'notin' => '&#8713;',
'ni' => '&#8715;',
'prod' => '&#8719;',
'sum' => '&#8721;',
'minus' => '&#8722;',
'lowast' => '&#8727;',
'radic' => '&#8730;',
'prop' => '&#8733;',
'infin' => '&#8734;',
'ang' => '&#8736;',
'and' => '&#8743;',
'or' => '&#8744;',
'cap' => '&#8745;',
'cup' => '&#8746;',
'int' => '&#8747;',
'there4' => '&#8756;',
'sim' => '&#8764;',
'cong' => '&#8773;',
'asymp' => '&#8776;',
'ne' => '&#8800;',
'equiv' => '&#8801;',
'le' => '&#8804;',
'ge' => '&#8805;',
'sub' => '&#8834;',
'sup' => '&#8835;',
'nsub' => '&#8836;',
'sube' => '&#8838;',
'supe' => '&#8839;',
'oplus' => '&#8853;',
'otimes' => '&#8855;',
'perp' => '&#8869;',
'sdot' => '&#8901;',
'lceil' => '&#8968;',
'rceil' => '&#8969;',
'lfloor' => '&#8970;',
'rfloor' => '&#8971;',
'lang' => '&#9001;',
'rang' => '&#9002;',
'loz' => '&#9674;',
'spades' => '&#9824;',
'clubs' => '&#9827;',
'hearts' => '&#9829;',
'diams' => '&#9830;',
'nbsp' => '&#160;',
'iexcl' => '&#161;',
'cent' => '&#162;',
'pound' => '&#163;',
'curren' => '&#164;',
'yen' => '&#165;',
'brvbar' => '&#166;',
'sect' => '&#167;',
'uml' => '&#168;',
'copy' => '&#169;',
'ordf' => '&#170;',
'laquo' => '&#171;',
'not' => '&#172;',
'shy' => '&#173;',
'reg' => '&#174;',
'macr' => '&#175;',
'deg' => '&#176;',
'plusmn' => '&#177;',
'sup2' => '&#178;',
'sup3' => '&#179;',
'acute' => '&#180;',
'micro' => '&#181;',
'para' => '&#182;',
'middot' => '&#183;',
'cedil' => '&#184;',
'sup1' => '&#185;',
'ordm' => '&#186;',
'raquo' => '&#187;',
'frac14' => '&#188;',
'frac12' => '&#189;',
'frac34' => '&#190;',
'iquest' => '&#191;',
'Agrave' => '&#192;',
'Aacute' => '&#193;',
'Acirc' => '&#194;',
'Atilde' => '&#195;',
'Auml' => '&#196;',
'Aring' => '&#197;',
'AElig' => '&#198;',
'Ccedil' => '&#199;',
'Egrave' => '&#200;',
'Eacute' => '&#201;',
'Ecirc' => '&#202;',
'Euml' => '&#203;',
'Igrave' => '&#204;',
'Iacute' => '&#205;',
'Icirc' => '&#206;',
'Iuml' => '&#207;',
'ETH' => '&#208;',
'Ntilde' => '&#209;',
'Ograve' => '&#210;',
'Oacute' => '&#211;',
'Ocirc' => '&#212;',
'Otilde' => '&#213;',
'Ouml' => '&#214;',
'times' => '&#215;',
'Oslash' => '&#216;',
'Ugrave' => '&#217;',
'Uacute' => '&#218;',
'Ucirc' => '&#219;',
'Uuml' => '&#220;',
'Yacute' => '&#221;',
'THORN' => '&#222;',
'szlig' => '&#223;',
'agrave' => '&#224;',
'aacute' => '&#225;',
'acirc' => '&#226;',
'atilde' => '&#227;',
'auml' => '&#228;',
'aring' => '&#229;',
'aelig' => '&#230;',
'ccedil' => '&#231;',
'egrave' => '&#232;',
'eacute' => '&#233;',
'ecirc' => '&#234;',
'euml' => '&#235;',
'igrave' => '&#236;',
'iacute' => '&#237;',
'icirc' => '&#238;',
'iuml' => '&#239;',
'eth' => '&#240;',
'ntilde' => '&#241;',
'ograve' => '&#242;',
'oacute' => '&#243;',
'ocirc' => '&#244;',
'otilde' => '&#245;',
'ouml' => '&#246;',
'divide' => '&#247;',
'oslash' => '&#248;',
'ugrave' => '&#249;',
'uacute' => '&#250;',
'ucirc' => '&#251;',
'uuml' => '&#252;',
'yacute' => '&#253;',
'thorn' => '&#254;',
'yuml' => '&#255;',
);
// Entity not found? Destroy it.
return isset($table[$matches[1]]) ? $table[$matches[1]] : '';
}

View File

@@ -0,0 +1,54 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
include_once 'f_core/f_classes/class.be.servers.php';
$queue = new fileQueue();
if ($cfg["video_module"] == 1 and $queue->load("video")) {
if ($queue->check()) {
$queue->startTransfer();
echo "\n";
}
}
if ($cfg["image_module"] == 1 and $queue->load("image")) {
if ($queue->check()) {
$queue->startTransfer();
echo "\n";
}
}
if ($cfg["audio_module"] == 1 and $queue->load("audio")) {
if ($queue->check()) {
$queue->startTransfer();
echo "\n";
}
}
if ($cfg["document_module"] == 1 and $queue->load("doc")) {
if ($queue->check()) {
$queue->startTransfer();
echo "\n";
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
set_time_limit(0);
$main_dir = realpath(dirname(__FILE__) . '/../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$act = VSubscriber::update_followers_subs();

View File

@@ -0,0 +1,11 @@
<?php
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
/* path to recordings */
$path = getenv('VOD_REC_PATH') ?: '/mnt/rec';
/* main url */
$base = getenv('CRON_BASE_URL') ?: 'http://localhost:8080';
/* cron salt key */
$ssk = getenv('CRON_SSK') ?: 'CHANGE_ME_IN_BACKEND';

View File

@@ -0,0 +1,64 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
set_time_limit(0);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
require 'cfg.php';
$main_dir = realpath(dirname(__FILE__) . '/../../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$log = $path . '/.clear_vods.log';
error_log(sprintf("[%s] clear_vods cron task...\n\n", date("Y-m-d H:i:s")), 3, $log);
$cmd = 'ls ' . $path . '/*out.mp4';
exec($cmd, $out);
if ($out[0]) {
foreach ($out as $file) {
if (file_exists($file)) {
$base = str_replace(array($path . '/', '.mp4'), array('', ''), $file);
$sql = sprintf("SELECT `db_id` FROM `db_livefiles` WHERE `file_name`='%s' LIMIT 1;", $base);
$rs = $db->execute($sql);
if (!$rs->fields["db_id"]) {
$a = explode("-", $file);
$l = str_replace('out.mp4', 'p.mp4', $a[1]);
$preview = $a[0] . $l;
//echo sprintf("can delete %s\n", $file);
//echo sprintf("can delete %s\n", $preview);
if (unlink($file)) {
error_log(sprintf("[%s] unlinked %s\n", date("Y-m-d H:i:s"), $file), 3, $log);
} else {
error_log(sprintf("[%s] failed unlink %s\n", date("Y-m-d H:i:s"), $file), 3, $log);
}
if (file_exists($preview)) {
if (unlink($preview)) {
error_log(sprintf("[%s] unlinked %s\n", date("Y-m-d H:i:s"), $preview), 3, $log);
} else {
error_log(sprintf("[%s] failed unlink %s\n", date("Y-m-d H:i:s"), $preview), 3, $log);
}
}
}
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
set_time_limit(0);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
require 'cfg.php';
$main_dir = realpath(dirname(__FILE__) . '/../../../../');
set_include_path($main_dir);
include_once 'f_core/config.core.php';
$commands = array();
$found = 0;
exec("ps ax", $commands);
if (count($commands) > 0) {
foreach ($commands as $command) {
if (strpos($command, 'ffmpeg') === false) {
} else {
$found = 1;
}
}
}
if ($found) {
exit;
}
$ffmpeg = '/usr/bin/ffmpeg';
$log = $path . '/.recording_fix.log';
$cmd = 'ls ' . $path . '/*out.mp4';
error_log(sprintf("[%s] recording_fix cron task...\n\n", date("Y-m-d H:i:s")), 3, $log);
exec($cmd, $out);
if ($out[0]) {
foreach ($out as $file) {
if (!is_file($file) or (is_file($file) and filesize($file) == 0)) {
$flv = str_replace(".mp4", ".flv", $file);
if (file_exists($flv) and filesize($flv) > 0) {
$cmd_ffmpeg = sprintf("%s -y -i %s -codec copy -movflags +faststart %s", $ffmpeg, $flv, $file);
error_log(sprintf("[%s] %s\n", date("Y-m-d H:i:s"), $cmd_ffmpeg), 3, $log);
exec($cmd_ffmpeg . ' 2>&1', $out_ffmpeg);
$result = implode("\n", $out_ffmpeg);
VFileinfo::write($log, $result . "\n\n\n", true);
}
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
require 'cfg.php';
$cmd = 'ls ' . $path . '/*out.mp4';
exec($cmd, $out);
if ($out[0]) {
foreach ($out as $file) {
if (file_exists($file)) {
$a = explode("-", $file);
$l = str_replace('out.mp4', 'p.mp4', $a[1]);
$preview = $a[0] . $l;
if (!file_exists($preview)) {
$cmd = 'ffmpeg -y -t 30 -i ' . $file . ' -codec copy -movflags +faststart ' . $preview;
exec(escapeshellcmd($cmd) . ' >/dev/null &');
}
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
define('_SERVER_SLUG', 'vods1-local');
require 'cfg.php';
$url = $base . '/syncdf?s=';
$df = disk_free_space("/");
$free = $df;
$date = date("Y-m-d");
$tk = md5($date . $ssk);
$url = sprintf("%s%s&a=%s&_=%s", $url, $tk, _SERVER_SLUG, $free);
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
$data = curl_exec($curl);
curl_close($curl);

View File

@@ -0,0 +1,73 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
ini_set("error_reporting", E_ALL & ~E_STRICT & ~E_NOTICE & ~E_DEPRECATED);
define('_ISVALID', true);
require 'cfg.php';
$url = $base . '/syncvods?s=';
$date = date("Y-m-d");
$tk = md5($date . $ssk);
$url .= $tk;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
$data = curl_exec($curl);
curl_close($curl);
$list = json_decode($data);
$log = $path . '/.sync_vods.log';
error_log(sprintf("[%s] sync_vods cron task...\n\n", date("Y-m-d H:i:s")), 3, $log);
if ($list[0]) {
foreach ($list as $filename) {
$vod = $path . '/' . $filename . '.mp4';
if (file_exists($vod)) {
if (unlink($vod)) {
error_log(sprintf("[%s] unlinked %s\n", date("Y-m-d H:i:s"), $vod), 3, $log);
} else {
error_log(sprintf("[%s] failed unlink %s\n", date("Y-m-d H:i:s"), $vod), 3, $log);
}
}
$flv = $path . '/' . $filename . '.flv';
if (file_exists($flv)) {
if (unlink($flv)) {
error_log(sprintf("[%s] unlinked %s\n", date("Y-m-d H:i:s"), $flv), 3, $log);
} else {
error_log(sprintf("[%s] failed unlink %s\n", date("Y-m-d H:i:s"), $flv), 3, $log);
}
}
$ff = explode("-", $filename);
$_ff = str_replace('out', 'p', $ff[1]);
$pv = $path . '/' . $ff[0] . $_ff . '.mp4';
if (file_exists($pv)) {
if (unlink($pv)) {
error_log(sprintf("[%s] unlinked %s\n", date("Y-m-d H:i:s"), $pv), 3, $log);
} else {
error_log(sprintf("[%s] failed unlink %s\n", date("Y-m-d H:i:s"), $pv), 3, $log);
}
}
}
}

View File

@@ -0,0 +1,83 @@
# EasyStream Donations Module
A comprehensive donation system for EasyStream with Square integration, analytics, goals, and notifications.
## Structure
```
m_donations/
├── src/ # Source code
│ ├── Core/ # Core functionality
│ ├── Handlers/ # Business logic
│ ├── Models/ # Data models
│ └── Utils/ # Utilities
├── config/ # Configuration
├── sql/ # Database schemas
├── public/ # Public files
├── views/ # Templates
└── assets/ # Static assets
├── css/ # Styles
├── js/ # Scripts
└── img/ # Images
```
## Features
- Square payment integration
- Donation goals and milestones
- Real-time analytics
- Notification system
- RESTful API
- Rate limiting
- Security features
## Installation
1. Copy module files:
```bash
cp -r m_donations/ /path/to/easystream/f_modules/m_frontend/
```
2. Import database:
```bash
mysql -u your_username -p your_database < sql/install.sql
```
3. Configure:
- Copy `config/config.example.php` to `config/config.php`
- Update Square credentials
- Set webhook URL: `https://your-domain.com/f_modules/m_frontend/m_donations/public/webhook.php`
## Database Tables
- `donations` - Donation records
- `donation_goals` - Streamer goals
- `donation_milestones` - Goal milestones
- `donation_analytics` - Analytics data
- `donation_notifications` - System notifications
- `api_keys` - API authentication
- `api_rate_limits` - Rate limiting
## Security
- API key authentication
- Rate limiting
- Input validation
- XSS protection
- CSRF protection
- SQL injection prevention
## Development
1. Add database tables in `sql/install.sql`
2. Update `config/config.php`
3. Create model in `src/Models/`
4. Create handler in `src/Handlers/`
5. Add API endpoints in `api/index.php`
6. Create views in `views/`
7. Add assets in `assets/`
## Testing
- Unit tests: `phpunit tests/`
- API tests: `phpunit tests/api/`
- Integration tests: `phpunit tests/integration/`
## Support
Contact: Sami Ahmed
## License
EasyStream License Agreement

View File

@@ -0,0 +1,300 @@
# 🌧️ Rainforest Pay Integration for EasyStream
## 📋 **Complete Integration Package**
This is a comprehensive Rainforest Pay integration for EasyStream that provides:
-**Full Payment Processing** - Donations, tips, and creator monetization
-**Multiple Payment Methods** - Cards, bank transfers, mobile money, wallets
-**Secure Webhooks** - Real-time payment notifications
-**Payout System** - Automated creator payments
-**Admin Dashboard** - Transaction monitoring and management
-**User-Friendly Interface** - Modern donation forms and flows
## 🚀 **Quick Setup Guide**
### 1. **Database Setup**
```sql
-- Run the installation SQL
mysql -u your_user -p your_database < install_rainforest.sql
```
### 2. **Configuration**
Edit `config.rainforest.php` with your Rainforest Pay credentials:
```php
'api_key' => 'your_rainforest_api_key',
'secret_key' => 'your_rainforest_secret_key',
'merchant_id' => 'your_merchant_id',
'environment' => 'sandbox', // or 'production'
```
### 3. **Environment Variables** (Recommended)
```bash
# Add to your .env file
RAINFOREST_API_KEY=your_api_key_here
RAINFOREST_SECRET_KEY=your_secret_key_here
RAINFOREST_MERCHANT_ID=your_merchant_id_here
RAINFOREST_ENVIRONMENT=sandbox
RAINFOREST_WEBHOOK_URL=https://yourdomain.com/donations/webhook
RAINFOREST_WEBHOOK_SECRET=your_webhook_secret_here
```
### 4. **Webhook Setup**
Configure your Rainforest Pay webhook URL to:
```
https://yourdomain.com/f_modules/m_frontend/m_donations/rainforest_webhook.php
```
## 📁 **File Structure**
```
f_modules/m_frontend/m_donations/
├── config.rainforest.php # Configuration settings
├── rainforest_pay.php # Main payment handler class
├── rainforest_donation_form.php # User donation interface
├── rainforest_webhook.php # Webhook processor
├── install_rainforest.sql # Database setup
└── README_RAINFOREST.md # This documentation
```
## 🎯 **Integration Points**
### **Donation Form URL**
```
/f_modules/m_frontend/m_donations/rainforest_donation_form.php?streamer=USER_ID
```
### **API Endpoints**
```php
// Create donation
POST /f_modules/m_frontend/m_donations/rainforest_pay.php?action=create_donation
// Process webhook
POST /f_modules/m_frontend/m_donations/rainforest_webhook.php
// Request payout
POST /f_modules/m_frontend/m_donations/rainforest_pay.php?action=request_payout
// Get payment methods
GET /f_modules/m_frontend/m_donations/rainforest_pay.php?action=get_payment_methods
```
## 💳 **Supported Payment Methods**
- **💳 Credit/Debit Cards** - Visa, Mastercard, American Express
- **🏦 Bank Transfers** - Direct bank account transfers
- **📱 Mobile Money** - MTN, Airtel, Vodafone, and other providers
- **👛 Digital Wallets** - PayPal, Apple Pay, Google Pay
## 🔧 **Usage Examples**
### **Basic Donation Processing**
```php
$rainforest = new RainforestPayHandler($class_database);
$result = $rainforest->createDonation(
$streamer_id = 123,
$amount = 25.00,
$donor_name = 'John Doe',
$message = 'Great content!',
$payment_method = 'card'
);
if ($result['success']) {
// Redirect to payment URL
header('Location: ' . $result['payment_url']);
} else {
// Handle error
echo $result['message'];
}
```
### **Payout Request**
```php
$result = $rainforest->requestPayout($streamer_id = 123);
if ($result['success']) {
echo "Payout of $" . $result['amount'] . " requested successfully";
} else {
echo "Payout failed: " . $result['message'];
}
```
### **Get Donation History**
```php
$donations = $rainforest->getDonationHistory($streamer_id = 123, $limit = 20);
foreach ($donations as $donation) {
echo "Donation: $" . $donation['amount'] . " from " . $donation['donor_name'];
}
```
## 🎨 **Frontend Integration**
### **Add Donation Button to Channel Pages**
```html
<a href="/f_modules/m_frontend/m_donations/rainforest_donation_form.php?streamer=<?php echo $streamer_id; ?>"
class="donate-btn">
💖 Donate
</a>
```
### **Add to Video Player**
```html
<button onclick="openDonationForm(<?php echo $streamer_id; ?>)" class="player-donate-btn">
💰 Support Creator
</button>
```
### **JavaScript Integration**
```javascript
function openDonationForm(streamerId) {
const url = `/f_modules/m_frontend/m_donations/rainforest_donation_form.php?streamer=${streamerId}`;
window.open(url, 'donation', 'width=600,height=800,scrollbars=yes');
}
```
## 🔒 **Security Features**
-**Webhook Signature Verification** - Ensures authentic notifications
-**Data Encryption** - Sensitive data is encrypted
-**Rate Limiting** - Prevents abuse and spam
-**Fraud Detection** - Built-in fraud prevention
-**Audit Logging** - Complete transaction audit trail
## 📊 **Admin Features**
### **Transaction Monitoring**
```php
// Get platform earnings
$sql = "SELECT SUM(platform_fee) as total_fees FROM donations WHERE status = 'completed'";
// Get top earners
$sql = "SELECT streamer_id, SUM(streamer_amount) as earnings
FROM donations
WHERE status = 'completed'
GROUP BY streamer_id
ORDER BY earnings DESC
LIMIT 10";
```
### **Payout Management**
```php
// Get pending payouts
$sql = "SELECT * FROM payouts WHERE status = 'pending' ORDER BY created_at ASC";
// Process automatic payouts
$rainforest->processAutomaticPayouts();
```
## 🎯 **Customization Options**
### **Fee Configuration**
```php
// In config.rainforest.php
'platform_fee_percentage' => 2.5, // 2.5% platform fee
'platform_fee_fixed' => 0.30, // $0.30 fixed fee
'payout_fee_percentage' => 1.0, // 1% payout fee
'payout_fee_fixed' => 0.25, // $0.25 payout fee
```
### **Donation Limits**
```php
'min_donation' => 1.00, // Minimum $1
'max_donation' => 10000.00, // Maximum $10,000
'min_payout' => 10.00, // Minimum payout $10
```
### **Payment Methods**
```php
'payment_methods' => [
'card' => true, // Enable/disable cards
'bank_transfer' => true, // Enable/disable bank transfers
'mobile_money' => true, // Enable/disable mobile money
'wallet' => false // Enable/disable wallets
]
```
## 🚨 **Error Handling**
The integration includes comprehensive error handling:
- **Invalid amounts** - Validates min/max donation limits
- **Payment failures** - Graceful handling of failed payments
- **Network issues** - Retry logic for API calls
- **Webhook validation** - Signature verification for security
- **Database errors** - Transaction rollback on failures
## 📈 **Analytics & Reporting**
### **Built-in Views**
- `donation_summary` - Overall donation statistics per streamer
- `monthly_donation_stats` - Monthly earnings breakdown
### **Custom Reports**
```sql
-- Top donors
SELECT donor_name, SUM(amount) as total_donated
FROM donations
WHERE status = 'completed'
GROUP BY donor_name
ORDER BY total_donated DESC;
-- Daily earnings
SELECT DATE(completed_at) as date, SUM(streamer_amount) as earnings
FROM donations
WHERE status = 'completed'
GROUP BY DATE(completed_at)
ORDER BY date DESC;
```
## 🔧 **Troubleshooting**
### **Common Issues**
1. **Webhook not receiving notifications**
- Check webhook URL configuration
- Verify SSL certificate
- Check firewall settings
2. **Payment failures**
- Verify API credentials
- Check environment setting (sandbox/production)
- Review error logs
3. **Database connection issues**
- Verify database credentials
- Check table permissions
- Run installation SQL
### **Debug Mode**
Enable debug logging in `config.rainforest.php`:
```php
'debug' => true,
'log_level' => 'debug'
```
## 📞 **Support**
For Rainforest Pay specific issues:
- 📧 **Email**: support@rainforestpay.com
- 📚 **Documentation**: https://docs.rainforestpay.com
- 🔧 **API Reference**: https://api.rainforestpay.com/docs
For EasyStream integration issues:
- Check the error logs in `logs/rainforest_pay.log`
- Review webhook logs in `logs/rainforest_webhooks.log`
- Verify database table structure matches installation SQL
## 🎉 **Ready to Go!**
Your Rainforest Pay integration is now complete and ready for production use. The system provides:
- **Seamless donation processing** for creators
- **Multiple payment options** for donors
- **Automated payout system** for creators
- **Complete admin oversight** for platform owners
- **Secure, scalable architecture** for growth
**Happy monetizing!** 💰🚀

View File

@@ -0,0 +1,292 @@
# EasyStream Donations API Documentation
This API allows external applications to interact with the EasyStream donation system. All requests require authentication using an API key.
## Authentication
All API requests must include an API key in the Authorization header:
```
Authorization: your-api-key-here
```
## Base URL
```
https://your-domain.com/f_modules/m_frontend/m_donations/api
```
## Endpoints
### Analytics
#### Get Analytics Data
```http
GET /analytics
```
Query Parameters:
- `start_date` (optional): Start date in YYYY-MM-DD format (default: 30 days ago)
- `end_date` (optional): End date in YYYY-MM-DD format (default: today)
Response:
```json
[
{
"date": "2024-03-20",
"total_donations": 5,
"total_amount": 150.00,
"average_donation": 30.00,
"unique_donors": 3
}
]
```
#### Get Analytics Summary
```http
GET /analytics/summary
```
Response:
```json
{
"total_donations": 150,
"total_amount": 5000.00,
"average_donation": 33.33,
"unique_donors": 45,
"largest_donation": 200.00,
"smallest_donation": 5.00
}
```
#### Get Top Donors
```http
GET /analytics/top-donors
```
Query Parameters:
- `limit` (optional): Number of top donors to return (default: 10)
Response:
```json
[
{
"username": "donor1",
"display_name": "John Doe",
"donation_count": 15,
"total_amount": 500.00
}
]
```
### Goals
#### Get All Goals
```http
GET /goals
```
Response:
```json
[
{
"goal_id": 1,
"title": "New Equipment",
"description": "Help me upgrade my streaming setup",
"target_amount": 1000.00,
"current_amount": 500.00,
"start_date": "2024-03-01T00:00:00Z",
"end_date": "2024-04-01T00:00:00Z",
"status": "active"
}
]
```
#### Create New Goal
```http
POST /goals
```
Request Body:
```json
{
"title": "New Equipment",
"description": "Help me upgrade my streaming setup",
"target_amount": 1000.00,
"end_date": "2024-04-01"
}
```
Response:
```json
{
"success": true,
"goal_id": 1
}
```
#### Get Active Goals
```http
GET /goals/active
```
Response:
```json
[
{
"goal_id": 1,
"title": "New Equipment",
"description": "Help me upgrade my streaming setup",
"target_amount": 1000.00,
"current_amount": 500.00,
"start_date": "2024-03-01T00:00:00Z",
"end_date": "2024-04-01T00:00:00Z",
"status": "active"
}
]
```
#### Add Milestone to Goal
```http
POST /goals/milestones
```
Request Body:
```json
{
"goal_id": 1,
"title": "Halfway There",
"description": "Reach 50% of the goal",
"target_amount": 500.00,
"reward_description": "Special shoutout on stream"
}
```
Response:
```json
{
"success": true,
"milestone_id": 1
}
```
### Notifications
#### Get All Notifications
```http
GET /notifications
```
Query Parameters:
- `limit` (optional): Number of notifications to return (default: 20)
Response:
```json
[
{
"notification_id": 1,
"type": "donation",
"title": "New Donation",
"message": "Received $50.00 from John Doe",
"is_read": false,
"created_at": "2024-03-20T15:30:00Z"
}
]
```
#### Get Unread Notifications
```http
GET /notifications/unread
```
Query Parameters:
- `limit` (optional): Number of notifications to return (default: 10)
Response:
```json
[
{
"notification_id": 1,
"type": "donation",
"title": "New Donation",
"message": "Received $50.00 from John Doe",
"is_read": false,
"created_at": "2024-03-20T15:30:00Z"
}
]
```
#### Mark Notifications as Read
```http
POST /notifications/unread
```
Request Body:
```json
{
"notification_ids": [1, 2, 3]
}
```
Response:
```json
{
"success": true
}
```
## Error Responses
All endpoints may return the following error responses:
### 400 Bad Request
```json
{
"error": "Missing required fields"
}
```
### 401 Unauthorized
```json
{
"error": "API key is required"
}
```
or
```json
{
"error": "Invalid API key"
}
```
### 404 Not Found
```json
{
"error": "Endpoint not found"
}
```
### 405 Method Not Allowed
```json
{
"error": "Method not allowed"
}
```
## Rate Limiting
The API is rate limited to 100 requests per minute per API key. When the rate limit is exceeded, the API will return a 429 Too Many Requests response:
```json
{
"error": "Rate limit exceeded"
}
```
## Best Practices
1. Always include error handling in your API calls
2. Cache responses when appropriate to reduce API calls
3. Use appropriate HTTP methods (GET for retrieving data, POST for creating)
4. Include proper error messages in your application when API calls fail
5. Keep your API key secure and never expose it in client-side code

View File

@@ -0,0 +1,194 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
require_once __DIR__ . '/../config/config.php';
use Donations\AnalyticsHandler;
use Donations\GoalHandler;
use Donations\NotificationHandler;
// Set headers
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// Get request method and path
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = str_replace('/f_modules/m_frontend/m_donations/api', '', $path);
$path = trim($path, '/');
// Get request body
$body = json_decode(file_get_contents('php://input'), true);
// Validate API key
$headers = getallheaders();
$api_key = $headers['Authorization'] ?? null;
if (!$api_key) {
http_response_code(401);
echo json_encode(['error' => 'API key is required']);
exit();
}
// Validate API key against database
$sql = "SELECT user_id FROM api_keys WHERE api_key = ? AND is_active = 1";
$user = db()->getRow($sql, [$api_key]);
if (!$user) {
http_response_code(401);
echo json_encode(['error' => 'Invalid API key']);
exit();
}
$streamer_id = $user['user_id'];
// Route requests
try {
switch ($path) {
case 'analytics':
$handler = new AnalyticsHandler();
switch ($method) {
case 'GET':
$start_date = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
$end_date = $_GET['end_date'] ?? date('Y-m-d');
echo json_encode($handler->getAnalytics($streamer_id, $start_date, $end_date));
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'analytics/summary':
$handler = new AnalyticsHandler();
switch ($method) {
case 'GET':
echo json_encode($handler->getSummary($streamer_id));
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'analytics/top-donors':
$handler = new AnalyticsHandler();
switch ($method) {
case 'GET':
$limit = $_GET['limit'] ?? 10;
echo json_encode($handler->getTopDonors($streamer_id, $limit));
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'goals':
$handler = new GoalHandler();
switch ($method) {
case 'GET':
echo json_encode($handler->getStreamerGoals($streamer_id));
break;
case 'POST':
if (!isset($body['title']) || !isset($body['target_amount'])) {
throw new Exception('Missing required fields');
}
$goal_id = $handler->createGoal(
$streamer_id,
$body['title'],
$body['description'] ?? '',
$body['target_amount'],
$body['end_date'] ?? null
);
echo json_encode(['success' => true, 'goal_id' => $goal_id]);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'goals/active':
$handler = new GoalHandler();
switch ($method) {
case 'GET':
echo json_encode($handler->getActiveGoals($streamer_id));
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'goals/milestones':
$handler = new GoalHandler();
switch ($method) {
case 'POST':
if (!isset($body['goal_id']) || !isset($body['title']) || !isset($body['target_amount'])) {
throw new Exception('Missing required fields');
}
$milestone_id = $handler->addMilestone(
$body['goal_id'],
$body['title'],
$body['description'] ?? '',
$body['target_amount'],
$body['reward_description'] ?? ''
);
echo json_encode(['success' => true, 'milestone_id' => $milestone_id]);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'notifications':
$handler = new NotificationHandler();
switch ($method) {
case 'GET':
$limit = $_GET['limit'] ?? 20;
echo json_encode($handler->getAllNotifications($streamer_id, $limit));
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'notifications/unread':
$handler = new NotificationHandler();
switch ($method) {
case 'GET':
$limit = $_GET['limit'] ?? 10;
echo json_encode($handler->getUnreadNotifications($streamer_id, $limit));
break;
case 'POST':
if (!isset($body['notification_ids'])) {
throw new Exception('Missing notification IDs');
}
$success = $handler->markAsRead($body['notification_ids']);
echo json_encode(['success' => $success]);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Endpoint not found']);
}
} catch (Exception $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
}

View File

@@ -0,0 +1,56 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
header('Content-Type: application/json');
$usr_key = $class_filter->clr_str($_GET['u']);
if ($usr_key == '') { echo json_encode(['recent'=>[], 'goal'=>['title'=>'','raised'=>0,'target'=>0]]); exit; }
// map usr_key -> usr_id
$usr_id = (int) $class_database->singleFieldValue('db_accountuser','usr_id','usr_key',$usr_key);
$recent = [];
$goal = ['title'=>'','raised'=>0,'target'=>0];
try {
// Donations may be stored by the donations module; try common table names
// recent donations
$q = $db->execute(sprintf("SELECT `donor_name`,`amount`,`message`,`created_at` FROM `donations` WHERE `channel_usr_id`='%s' ORDER BY `created_at` DESC LIMIT 5;", $usr_id));
while(!$q->EOF){
$recent[] = [
'name' => $q->fields['donor_name'],
'amount' => (float) $q->fields['amount'],
'message' => $q->fields['message'],
'time' => $q->fields['created_at'],
];
$q->MoveNext();
}
} catch (Exception $e) { /* table missing -> ignore */ }
try {
$g = $db->execute(sprintf("SELECT `title`,`target_amount`,`current_amount` FROM `donation_goals` WHERE `channel_usr_id`='%s' AND `active`='1' ORDER BY `id` DESC LIMIT 1;", $usr_id));
if ($g->fields['title'] != ''){
$goal['title'] = $g->fields['title'];
$goal['target'] = (float) $g->fields['target_amount'];
$goal['raised'] = (float) $g->fields['current_amount'];
}
} catch (Exception $e) { /* ignore */ }
echo json_encode(['recent'=>$recent, 'goal'=>$goal]);

View File

@@ -0,0 +1,74 @@
<?php
/*******************************************************************************************************************
| Rainforest Pay Configuration for EasyStream
| Integration with Rainforest Pay payment gateway
|*******************************************************************************************************************/
return [
'rainforest' => [
// API Configuration
'api_key' => getenv('RAINFOREST_API_KEY') ?: 'your_api_key_here',
'secret_key' => getenv('RAINFOREST_SECRET_KEY') ?: 'your_secret_key_here',
'merchant_id' => getenv('RAINFOREST_MERCHANT_ID') ?: 'your_merchant_id_here',
// Environment Settings
'environment' => getenv('RAINFOREST_ENVIRONMENT') ?: 'sandbox', // 'sandbox' or 'production'
'api_base_url' => [
'sandbox' => 'https://api-sandbox.rainforestpay.com/v1',
'production' => 'https://api.rainforestpay.com/v1'
],
// Currency and Limits
'currency' => 'USD',
'min_donation' => 1.00,
'max_donation' => 10000.00,
// Supported Payment Methods
'payment_methods' => [
'card' => true, // Credit/Debit Cards
'bank_transfer' => true, // Bank Transfers
'mobile_money' => true, // Mobile Money (MTN, Airtel, etc.)
'crypto' => false, // Cryptocurrency (if supported)
'wallet' => true // Digital Wallets
],
// Webhook Configuration
'webhook_url' => getenv('RAINFOREST_WEBHOOK_URL') ?: 'https://yourdomain.com/donations/webhook',
'webhook_secret' => getenv('RAINFOREST_WEBHOOK_SECRET') ?: 'your_webhook_secret_here',
// Transaction Settings
'auto_capture' => true,
'timeout' => 300, // 5 minutes
// Fee Configuration
'platform_fee_percentage' => 2.5, // Platform fee percentage
'platform_fee_fixed' => 0.30, // Fixed platform fee
// Payout Settings
'min_payout' => 10.00,
'payout_schedule' => 'weekly', // 'daily', 'weekly', 'monthly'
'payout_fee_percentage' => 1.0,
'payout_fee_fixed' => 0.25
],
// Streamer Configuration
'streamer' => [
'min_balance' => 10.00,
'payout_fee' => 2.5, // Percentage
'payout_fee_fixed' => 0.50,
'auto_payout' => false,
'payout_threshold' => 100.00
],
// Security Settings
'security' => [
'encrypt_data' => true,
'log_transactions' => true,
'fraud_detection' => true,
'rate_limiting' => [
'max_attempts' => 5,
'time_window' => 3600 // 1 hour
]
]
];
?>

View File

@@ -0,0 +1,20 @@
<?php
// Square API Configuration
return [
'square' => [
'application_id' => 'YOUR_SQUARE_APP_ID',
'access_token' => 'YOUR_SQUARE_ACCESS_TOKEN',
'location_id' => 'YOUR_SQUARE_LOCATION_ID',
'environment' => 'sandbox', // Change to 'production' when going live
'currency' => 'USD',
'min_donation' => 1.00,
'max_donation' => 1000.00,
'default_amounts' => [5, 10, 25, 50, 100],
'webhook_secret' => 'YOUR_WEBHOOK_SECRET'
],
'streamer' => [
'min_balance' => 10.00, // Minimum balance before payout
'payout_fee' => 2.9, // Square payout fee percentage
'payout_fee_fixed' => 0.30 // Fixed fee per payout
]
];

View File

@@ -0,0 +1,109 @@
<?php
// Module paths
define('DONATIONS_PATH', __DIR__ . '/..');
define('DONATIONS_SRC', DONATIONS_PATH . '/src');
define('DONATIONS_PUBLIC', DONATIONS_PATH . '/public');
define('DONATIONS_VIEWS', DONATIONS_PATH . '/views');
define('DONATIONS_ASSETS', DONATIONS_PATH . '/assets');
// Autoloader
spl_autoload_register(function ($class) {
$file = DONATIONS_SRC . '/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) require_once $file;
});
// Helper functions
function redirect($url) {
header("Location: $url");
exit;
}
function json_response($data, $status = 200) {
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
function view($template, $data = []) {
extract($data);
require DONATIONS_VIEWS . "/$template.php";
}
function handle_error($message, $code = 500) {
if (request_wants_json()) {
json_response(['error' => $message], $code);
} else {
http_response_code($code);
view('error', ['message' => $message]);
}
}
function request_wants_json() {
return isset($_SERVER['HTTP_ACCEPT']) &&
strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false;
}
function csrf_token() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function verify_csrf_token($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
function db() {
global $class_database;
return $class_database;
}
// Module configuration
return [
'square' => [
'application_id' => 'YOUR_SQUARE_APP_ID',
'access_token' => 'YOUR_SQUARE_ACCESS_TOKEN',
'location_id' => 'YOUR_SQUARE_LOCATION_ID',
'environment' => 'sandbox',
'currency' => 'USD',
'min_donation' => 1.00,
'max_donation' => 1000.00,
'default_amounts' => [5, 10, 25, 50, 100],
'webhook_secret' => 'YOUR_WEBHOOK_SECRET'
],
'payout' => [
'min_balance' => 50.00,
'payout_fee' => 0.05,
'payout_fee_fixed' => 1.00
],
'api' => [
'rate_limit' => 100,
'rate_window' => 60,
'allowed_ips' => []
],
'tables' => [
'donations' => 'donations',
'donation_goals' => 'donation_goals',
'donation_milestones' => 'donation_milestones',
'donation_analytics' => 'donation_analytics',
'donation_notifications' => 'donation_notifications',
'api_keys' => 'api_keys',
'api_rate_limits' => 'api_rate_limits'
],
'notifications' => [
'retention_days' => 30,
'batch_size' => 100
],
'analytics' => [
'default_period' => 30,
'cache_duration' => 3600,
'min_data_points' => 10
],
'security' => [
'allowed_origins' => [],
'max_request_size' => '10M',
'session_timeout' => 3600
]
];

View File

@@ -0,0 +1,19 @@
<?php
return [
'square' => [
'application_id' => 'YOUR_SQUARE_APP_ID',
'access_token' => 'YOUR_SQUARE_ACCESS_TOKEN',
'location_id' => 'YOUR_SQUARE_LOCATION_ID',
'environment' => 'sandbox', // Change to 'production' when going live
'currency' => 'USD',
'min_donation' => 1.00,
'max_donation' => 1000.00,
'default_amounts' => [5, 10, 25, 50, 100],
'webhook_secret' => 'YOUR_WEBHOOK_SECRET'
],
'streamer' => [
'min_balance' => 10.00, // Minimum balance before payout
'payout_fee' => 2.9, // Square payout fee percentage
'payout_fee_fixed' => 0.30 // Fixed fee per payout
]
];

View File

@@ -0,0 +1,172 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
// Load Square configuration
$square_config = require_once __DIR__ . '/config.square.php';
// Include Square SDK
require_once __DIR__ . '/vendor/autoload.php';
use Square\SquareClient;
use Square\Environment;
use Square\Models\CreatePaymentRequest;
use Square\Models\Money;
class DonationHandler {
private $square_client;
private $config;
private $class_database;
public function __construct($class_database) {
$this->class_database = $class_database;
$this->config = require __DIR__ . '/config.square.php';
// Initialize Square client
$this->square_client = new SquareClient([
'accessToken' => $this->config['square']['access_token'],
'environment' => $this->config['square']['environment'] === 'production' ? Environment::PRODUCTION : Environment::SANDBOX
]);
}
public function createDonation($streamer_id, $amount, $donor_name = '', $message = '') {
try {
// Validate amount
if ($amount < $this->config['square']['min_donation'] || $amount > $this->config['square']['max_donation']) {
return ['success' => false, 'message' => 'Invalid donation amount'];
}
// Create payment request
$money = new Money();
$money->setAmount($amount * 100); // Convert to cents
$money->setCurrency($this->config['square']['currency']);
$payment_request = new CreatePaymentRequest();
$payment_request->setSourceId('EXTERNAL');
$payment_request->setAmountMoney($money);
$payment_request->setLocationId($this->config['square']['location_id']);
// Add metadata
$payment_request->setMetadata([
'streamer_id' => $streamer_id,
'donor_name' => $donor_name,
'message' => $message
]);
// Create payment
$payment = $this->square_client->getPaymentsApi()->createPayment($payment_request);
if ($payment->isSuccess()) {
// Record donation in database
$this->recordDonation($streamer_id, $amount, $donor_name, $message, $payment->getResult()->getPayment()->getId());
return [
'success' => true,
'payment_id' => $payment->getResult()->getPayment()->getId(),
'message' => 'Donation processed successfully'
];
} else {
return [
'success' => false,
'message' => 'Failed to process donation: ' . $payment->getErrors()[0]->getDetail()
];
}
} catch (Exception $e) {
return [
'success' => false,
'message' => 'Error processing donation: ' . $e->getMessage()
];
}
}
private function recordDonation($streamer_id, $amount, $donor_name, $message, $payment_id) {
$sql = "INSERT INTO donations (
streamer_id, amount, donor_name, message, payment_id, status, created_at
) VALUES (?, ?, ?, ?, ?, 'completed', NOW())";
$params = [$streamer_id, $amount, $donor_name, $message, $payment_id];
$this->class_database->executeQuery($sql, $params);
// Update streamer's balance
$sql = "UPDATE users SET donation_balance = donation_balance + ? WHERE user_id = ?";
$this->class_database->executeQuery($sql, [$amount, $streamer_id]);
}
public function getStreamerBalance($streamer_id) {
$sql = "SELECT donation_balance FROM users WHERE user_id = ?";
$result = $this->class_database->getRow($sql, [$streamer_id]);
return $result['donation_balance'] ?? 0;
}
public function requestPayout($streamer_id) {
try {
$balance = $this->getStreamerBalance($streamer_id);
if ($balance < $this->config['streamer']['min_balance']) {
return [
'success' => false,
'message' => 'Insufficient balance for payout'
];
}
// Calculate fees
$fee_amount = ($balance * ($this->config['streamer']['payout_fee'] / 100)) + $this->config['streamer']['payout_fee_fixed'];
$payout_amount = $balance - $fee_amount;
// Create payout request
$payout = $this->square_client->getPayoutsApi()->createPayout([
'amount_money' => [
'amount' => $payout_amount * 100, // Convert to cents
'currency' => $this->config['square']['currency']
],
'location_id' => $this->config['square']['location_id']
]);
if ($payout->isSuccess()) {
// Record payout in database
$this->recordPayout($streamer_id, $payout_amount, $fee_amount, $payout->getResult()->getPayout()->getId());
return [
'success' => true,
'message' => 'Payout processed successfully',
'amount' => $payout_amount
];
} else {
return [
'success' => false,
'message' => 'Failed to process payout: ' . $payout->getErrors()[0]->getDetail()
];
}
} catch (Exception $e) {
return [
'success' => false,
'message' => 'Error processing payout: ' . $e->getMessage()
];
}
}
private function recordPayout($streamer_id, $amount, $fee, $payout_id) {
// Record payout
$sql = "INSERT INTO payouts (
streamer_id, amount, fee, payout_id, status, created_at
) VALUES (?, ?, ?, ?, 'completed', NOW())";
$params = [$streamer_id, $amount, $fee, $payout_id];
$this->class_database->executeQuery($sql, $params);
// Reset streamer's balance
$sql = "UPDATE users SET donation_balance = 0 WHERE user_id = ?";
$this->class_database->executeQuery($sql, [$streamer_id]);
}
public function getDonationHistory($streamer_id, $limit = 10) {
$sql = "SELECT d.*, u.username as streamer_name
FROM donations d
JOIN users u ON d.streamer_id = u.user_id
WHERE d.streamer_id = ?
ORDER BY d.created_at DESC
LIMIT ?";
return $this->class_database->getRows($sql, [$streamer_id, $limit]);
}
}

View File

@@ -0,0 +1,150 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
// Load Square configuration
$square_config = require_once __DIR__ . '/config.square.php';
// Get streamer information
$streamer_id = $_GET['streamer_id'] ?? 0;
$sql = "SELECT username, display_name FROM users WHERE user_id = ?";
$streamer = $class_database->getRow($sql, [$streamer_id]);
if (!$streamer) {
die('Invalid streamer');
}
// Initialize donation handler
require_once __DIR__ . '/donate.php';
$donation_handler = new DonationHandler($class_database);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Donate to <?php echo htmlspecialchars($streamer['display_name'] ?? $streamer['username']); ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<style>
.donation-amount-btn {
margin: 5px;
min-width: 80px;
}
.custom-amount-input {
max-width: 150px;
}
.donation-form {
max-width: 500px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container py-5">
<div class="donation-form">
<h2 class="text-center mb-4">Donate to <?php echo htmlspecialchars($streamer['display_name'] ?? $streamer['username']); ?></h2>
<form id="donationForm" class="needs-validation" novalidate>
<input type="hidden" name="streamer_id" value="<?php echo $streamer_id; ?>">
<div class="mb-3">
<label for="donorName" class="form-label">Your Name (Optional)</label>
<input type="text" class="form-control" id="donorName" name="donor_name">
</div>
<div class="mb-3">
<label class="form-label">Donation Amount</label>
<div class="d-flex flex-wrap justify-content-center mb-3">
<?php foreach ($square_config['square']['default_amounts'] as $amount): ?>
<button type="button" class="btn btn-outline-primary donation-amount-btn"
data-amount="<?php echo $amount; ?>">
$<?php echo number_format($amount, 2); ?>
</button>
<?php endforeach; ?>
</div>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" class="form-control custom-amount-input" id="customAmount"
name="amount" min="<?php echo $square_config['square']['min_donation']; ?>"
max="<?php echo $square_config['square']['max_donation']; ?>"
step="0.01" required>
</div>
<div class="form-text">
Minimum: $<?php echo number_format($square_config['square']['min_donation'], 2); ?><br>
Maximum: $<?php echo number_format($square_config['square']['max_donation'], 2); ?>
</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">Message (Optional)</label>
<textarea class="form-control" id="message" name="message" rows="3"></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Donate Now</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('donationForm');
const customAmount = document.getElementById('customAmount');
const amountButtons = document.querySelectorAll('.donation-amount-btn');
// Handle preset amount buttons
amountButtons.forEach(button => {
button.addEventListener('click', function() {
const amount = this.dataset.amount;
customAmount.value = amount;
customAmount.focus();
});
});
// Form submission
form.addEventListener('submit', async function(e) {
e.preventDefault();
if (!form.checkValidity()) {
e.stopPropagation();
form.classList.add('was-validated');
return;
}
const formData = new FormData(form);
const data = {
streamer_id: formData.get('streamer_id'),
amount: parseFloat(formData.get('amount')),
donor_name: formData.get('donor_name'),
message: formData.get('message')
};
try {
const response = await fetch('process_donation.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
// Redirect to Square payment page
window.location.href = result.payment_url;
} else {
alert(result.message || 'An error occurred. Please try again.');
}
} catch (error) {
console.error('Error:', error);
alert('An error occurred. Please try again.');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,49 @@
-- Add donation_balance column to users table if it doesn't exist
ALTER TABLE users ADD COLUMN IF NOT EXISTS donation_balance DECIMAL(10,2) DEFAULT 0.00;
-- Create donations table
CREATE TABLE IF NOT EXISTS donations (
donation_id INT AUTO_INCREMENT PRIMARY KEY,
streamer_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
donor_name VARCHAR(255),
message TEXT,
payment_id VARCHAR(255) NOT NULL,
status ENUM('pending', 'completed', 'failed') NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id) ON DELETE CASCADE,
UNIQUE KEY unique_payment_id (payment_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create payouts table
CREATE TABLE IF NOT EXISTS payouts (
payout_id INT AUTO_INCREMENT PRIMARY KEY,
streamer_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
fee DECIMAL(10,2) NOT NULL,
payout_id VARCHAR(255) NOT NULL,
status ENUM('pending', 'processing', 'completed', 'failed') NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id) ON DELETE CASCADE,
UNIQUE KEY unique_payout_id (payout_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create webhook_logs table
CREATE TABLE IF NOT EXISTS webhook_logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
event_type VARCHAR(50) NOT NULL,
resource_id VARCHAR(255) NOT NULL,
streamer_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_donations_streamer_id ON donations(streamer_id);
CREATE INDEX IF NOT EXISTS idx_donations_status ON donations(status);
CREATE INDEX IF NOT EXISTS idx_payouts_streamer_id ON payouts(streamer_id);
CREATE INDEX IF NOT EXISTS idx_payouts_status ON payouts(status);
CREATE INDEX IF NOT EXISTS idx_webhook_logs_event_type ON webhook_logs(event_type);
CREATE INDEX IF NOT EXISTS idx_webhook_logs_resource_id ON webhook_logs(resource_id);

View File

@@ -0,0 +1,210 @@
-- Rainforest Pay Integration Database Tables for EasyStream
-- Run this SQL to create the necessary tables for Rainforest Pay integration
-- Donations table
CREATE TABLE IF NOT EXISTS `donations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`streamer_id` int(11) NOT NULL,
`donor_id` int(11) DEFAULT NULL,
`amount` decimal(10,2) NOT NULL,
`platform_fee` decimal(10,2) NOT NULL DEFAULT 0.00,
`streamer_amount` decimal(10,2) NOT NULL,
`donor_name` varchar(255) DEFAULT NULL,
`message` text DEFAULT NULL,
`payment_method` varchar(50) NOT NULL DEFAULT 'card',
`rainforest_payment_id` varchar(255) NOT NULL,
`status` enum('pending','completed','failed','cancelled','refunded') NOT NULL DEFAULT 'pending',
`rainforest_data` json DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completed_at` timestamp NULL DEFAULT NULL,
`failed_at` timestamp NULL DEFAULT NULL,
`failure_reason` text DEFAULT NULL,
`payout_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_streamer_id` (`streamer_id`),
KEY `idx_donor_id` (`donor_id`),
KEY `idx_rainforest_payment_id` (`rainforest_payment_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`),
KEY `idx_payout_id` (`payout_id`),
FOREIGN KEY (`streamer_id`) REFERENCES `db_accountuser` (`usr_id`) ON DELETE CASCADE,
FOREIGN KEY (`donor_id`) REFERENCES `db_accountuser` (`usr_id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Payouts table
CREATE TABLE IF NOT EXISTS `payouts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`streamer_id` int(11) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`fee` decimal(10,2) NOT NULL DEFAULT 0.00,
`rainforest_payout_id` varchar(255) NOT NULL,
`status` enum('pending','completed','failed','cancelled') NOT NULL DEFAULT 'pending',
`rainforest_data` json DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completed_at` timestamp NULL DEFAULT NULL,
`failed_at` timestamp NULL DEFAULT NULL,
`failure_reason` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_streamer_id` (`streamer_id`),
KEY `idx_rainforest_payout_id` (`rainforest_payout_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`),
FOREIGN KEY (`streamer_id`) REFERENCES `db_accountuser` (`usr_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Streamer payout settings table
CREATE TABLE IF NOT EXISTS `streamer_payout_settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`streamer_id` int(11) NOT NULL,
`payout_method` enum('bank_transfer','mobile_money','wallet','crypto') NOT NULL,
`payout_details` json NOT NULL,
`is_verified` tinyint(1) NOT NULL DEFAULT 0,
`auto_payout` tinyint(1) NOT NULL DEFAULT 0,
`payout_threshold` decimal(10,2) NOT NULL DEFAULT 10.00,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_streamer_id` (`streamer_id`),
FOREIGN KEY (`streamer_id`) REFERENCES `db_accountuser` (`usr_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Donation goals table (optional feature)
CREATE TABLE IF NOT EXISTS `donation_goals` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`streamer_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`description` text DEFAULT NULL,
`target_amount` decimal(10,2) NOT NULL,
`current_amount` decimal(10,2) NOT NULL DEFAULT 0.00,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completed_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_streamer_id` (`streamer_id`),
KEY `idx_is_active` (`is_active`),
FOREIGN KEY (`streamer_id`) REFERENCES `db_accountuser` (`usr_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Transaction logs table for audit trail
CREATE TABLE IF NOT EXISTS `rainforest_transaction_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_type` enum('donation','payout','webhook','refund') NOT NULL,
`transaction_id` varchar(255) NOT NULL,
`streamer_id` int(11) DEFAULT NULL,
`amount` decimal(10,2) DEFAULT NULL,
`status` varchar(50) NOT NULL,
`request_data` json DEFAULT NULL,
`response_data` json DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
`user_agent` text DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_transaction_type` (`transaction_type`),
KEY `idx_transaction_id` (`transaction_id`),
KEY `idx_streamer_id` (`streamer_id`),
KEY `idx_created_at` (`created_at`),
FOREIGN KEY (`streamer_id`) REFERENCES `db_accountuser` (`usr_id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Add donation balance column to existing user table
ALTER TABLE `db_accountuser`
ADD COLUMN `donation_balance` decimal(10,2) NOT NULL DEFAULT 0.00 AFTER `usr_affiliate`,
ADD COLUMN `total_donations_received` decimal(10,2) NOT NULL DEFAULT 0.00 AFTER `donation_balance`,
ADD COLUMN `total_donations_count` int(11) NOT NULL DEFAULT 0 AFTER `total_donations_received`,
ADD COLUMN `rainforest_customer_id` varchar(255) DEFAULT NULL AFTER `total_donations_count`,
ADD INDEX `idx_donation_balance` (`donation_balance`),
ADD INDEX `idx_rainforest_customer_id` (`rainforest_customer_id`);
-- Create indexes for better performance
CREATE INDEX `idx_donations_streamer_status` ON `donations` (`streamer_id`, `status`);
CREATE INDEX `idx_donations_completed_at` ON `donations` (`completed_at`);
CREATE INDEX `idx_payouts_streamer_status` ON `payouts` (`streamer_id`, `status`);
-- Insert default donation goals (optional)
INSERT INTO `donation_goals` (`streamer_id`, `title`, `description`, `target_amount`)
SELECT `usr_id`, 'Support My Content', 'Help me create better content for you!', 100.00
FROM `db_accountuser`
WHERE `usr_id` IN (1, 2, 3) -- Replace with actual streamer IDs
ON DUPLICATE KEY UPDATE `id` = `id`;
-- Create views for easy reporting
CREATE OR REPLACE VIEW `donation_summary` AS
SELECT
d.streamer_id,
u.usr_user as streamer_username,
u.usr_dname as streamer_display_name,
COUNT(d.id) as total_donations,
SUM(d.amount) as total_amount,
SUM(d.streamer_amount) as total_earned,
SUM(d.platform_fee) as total_fees,
AVG(d.amount) as average_donation,
MAX(d.amount) as largest_donation,
MIN(d.created_at) as first_donation,
MAX(d.completed_at) as latest_donation
FROM `donations` d
JOIN `db_accountuser` u ON d.streamer_id = u.usr_id
WHERE d.status = 'completed'
GROUP BY d.streamer_id;
CREATE OR REPLACE VIEW `monthly_donation_stats` AS
SELECT
d.streamer_id,
u.usr_user as streamer_username,
YEAR(d.completed_at) as year,
MONTH(d.completed_at) as month,
COUNT(d.id) as donations_count,
SUM(d.amount) as total_amount,
SUM(d.streamer_amount) as streamer_earnings,
SUM(d.platform_fee) as platform_fees
FROM `donations` d
JOIN `db_accountuser` u ON d.streamer_id = u.usr_id
WHERE d.status = 'completed'
GROUP BY d.streamer_id, YEAR(d.completed_at), MONTH(d.completed_at)
ORDER BY year DESC, month DESC;
-- Insert sample configuration data
INSERT INTO `db_settings` (`setting_key`, `setting_value`, `setting_description`) VALUES
('rainforest_pay_enabled', '1', 'Enable Rainforest Pay integration'),
('rainforest_pay_environment', 'sandbox', 'Rainforest Pay environment (sandbox/production)'),
('rainforest_pay_min_donation', '1.00', 'Minimum donation amount'),
('rainforest_pay_max_donation', '10000.00', 'Maximum donation amount'),
('rainforest_pay_platform_fee', '2.5', 'Platform fee percentage'),
('rainforest_pay_platform_fee_fixed', '0.30', 'Fixed platform fee amount')
ON DUPLICATE KEY UPDATE `setting_value` = VALUES(`setting_value`);
-- Create triggers for automatic balance updates
DELIMITER $$
CREATE TRIGGER `update_streamer_balance_after_donation`
AFTER UPDATE ON `donations`
FOR EACH ROW
BEGIN
IF NEW.status = 'completed' AND OLD.status != 'completed' THEN
UPDATE `db_accountuser`
SET
`donation_balance` = `donation_balance` + NEW.streamer_amount,
`total_donations_received` = `total_donations_received` + NEW.amount,
`total_donations_count` = `total_donations_count` + 1
WHERE `usr_id` = NEW.streamer_id;
END IF;
END$$
CREATE TRIGGER `update_streamer_balance_after_payout`
AFTER UPDATE ON `payouts`
FOR EACH ROW
BEGIN
IF NEW.status = 'completed' AND OLD.status != 'completed' THEN
UPDATE `db_accountuser`
SET `donation_balance` = `donation_balance` - (NEW.amount + NEW.fee)
WHERE `usr_id` = NEW.streamer_id;
END IF;
END$$
DELIMITER ;
-- Grant necessary permissions (adjust as needed)
-- GRANT SELECT, INSERT, UPDATE ON `donations` TO 'easystream_user'@'localhost';
-- GRANT SELECT, INSERT, UPDATE ON `payouts` TO 'easystream_user'@'localhost';
-- GRANT SELECT, INSERT, UPDATE ON `streamer_payout_settings` TO 'easystream_user'@'localhost';
COMMIT;

View File

@@ -0,0 +1,157 @@
-- Token System Database Tables
-- Complete monetization system with Rainforest Pay integration
-- Token purchases table
CREATE TABLE IF NOT EXISTS token_purchases (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
usd_amount DECIMAL(10,2) NOT NULL,
token_amount INT NOT NULL,
exchange_rate DECIMAL(10,4) NOT NULL,
payment_id VARCHAR(255) NULL,
status ENUM('pending', 'completed', 'failed', 'cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_payment_id (payment_id),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
);
-- Token redemptions table
CREATE TABLE IF NOT EXISTS token_redemptions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
token_amount INT NOT NULL,
gross_usd DECIMAL(10,2) NOT NULL,
platform_fee DECIMAL(10,2) NOT NULL,
net_usd DECIMAL(10,2) NOT NULL,
redemption_method ENUM('bank_transfer', 'mobile_money', 'paypal') NOT NULL,
payout_id INT NULL,
status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
failed_at TIMESTAMP NULL,
INDEX idx_user_id (user_id),
INDEX idx_payout_id (payout_id),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
);
-- Token payouts table (Rainforest Pay integration)
CREATE TABLE IF NOT EXISTS token_payouts (
id INT AUTO_INCREMENT PRIMARY KEY,
redemption_id INT NOT NULL,
user_id INT NOT NULL,
usd_amount DECIMAL(10,2) NOT NULL,
rainforest_payout_id VARCHAR(255) NULL,
status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending',
failure_reason TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
failed_at TIMESTAMP NULL,
INDEX idx_redemption_id (redemption_id),
INDEX idx_user_id (user_id),
INDEX idx_rainforest_payout_id (rainforest_payout_id),
INDEX idx_status (status),
FOREIGN KEY (redemption_id) REFERENCES token_redemptions(id) ON DELETE CASCADE
);
-- User payout settings table
CREATE TABLE IF NOT EXISTS user_payout_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE,
payout_method ENUM('bank_transfer', 'mobile_money', 'paypal') NOT NULL,
payout_details JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_payout_method (payout_method)
);
-- Token packages configuration (stored in db_settings)
INSERT INTO db_settings (setting_key, setting_value, setting_description) VALUES
('token_package_small', '{"tokens": 100, "bonus": 10, "usd": 10, "popular": false}', 'Small token package'),
('token_package_medium', '{"tokens": 500, "bonus": 75, "usd": 50, "popular": true}', 'Medium token package (most popular)'),
('token_package_large', '{"tokens": 1000, "bonus": 200, "usd": 100, "popular": false}', 'Large token package'),
('token_package_mega', '{"tokens": 2500, "bonus": 750, "usd": 250, "popular": false}', 'Mega token package')
ON DUPLICATE KEY UPDATE
setting_value = VALUES(setting_value),
setting_description = VALUES(setting_description);
-- Update token settings with monetization features
UPDATE db_settings SET setting_value = JSON_SET(
COALESCE(setting_value, '{}'),
'$.min_purchase', 5,
'$.max_purchase', 1000,
'$.min_redemption', 100,
'$.platform_fee_rate', 0.05,
'$.exchange_rate', 0.01
) WHERE setting_key = 'token_settings';
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_token_transactions_user_type ON token_transactions(user_id, transaction_type);
CREATE INDEX IF NOT EXISTS idx_token_transactions_created ON token_transactions(created_at);
-- Add donation balance column to users table if not exists
ALTER TABLE db_accountuser
ADD COLUMN IF NOT EXISTS donation_balance DECIMAL(10,2) DEFAULT 0.00,
ADD COLUMN IF NOT EXISTS token_balance INT DEFAULT 0;
-- Create view for user token balances
CREATE OR REPLACE VIEW user_token_balances AS
SELECT
u.usr_id as user_id,
u.usr_user as username,
COALESCE(SUM(CASE WHEN tt.transaction_type IN ('purchase', 'gift_received', 'refund') THEN tt.amount ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN tt.transaction_type IN ('gift_sent', 'redemption', 'spend') THEN tt.amount ELSE 0 END), 0) as token_balance,
COUNT(CASE WHEN tt.transaction_type = 'purchase' THEN 1 END) as total_purchases,
COALESCE(SUM(CASE WHEN tt.transaction_type = 'purchase' THEN tt.amount END), 0) as total_purchased_tokens,
COUNT(CASE WHEN tt.transaction_type = 'redemption' THEN 1 END) as total_redemptions,
COALESCE(SUM(CASE WHEN tt.transaction_type = 'redemption' THEN tt.amount END), 0) as total_redeemed_tokens
FROM db_accountuser u
LEFT JOIN token_transactions tt ON u.usr_id = tt.user_id
GROUP BY u.usr_id, u.usr_user;
-- Create view for token purchase analytics
CREATE OR REPLACE VIEW token_purchase_analytics AS
SELECT
DATE(tp.created_at) as purchase_date,
COUNT(*) as total_purchases,
SUM(tp.usd_amount) as total_usd,
SUM(tp.token_amount) as total_tokens,
AVG(tp.usd_amount) as avg_purchase_amount,
COUNT(DISTINCT tp.user_id) as unique_buyers
FROM token_purchases tp
WHERE tp.status = 'completed'
GROUP BY DATE(tp.created_at)
ORDER BY purchase_date DESC;
-- Create view for token redemption analytics
CREATE OR REPLACE VIEW token_redemption_analytics AS
SELECT
DATE(tr.created_at) as redemption_date,
COUNT(*) as total_redemptions,
SUM(tr.token_amount) as total_tokens_redeemed,
SUM(tr.gross_usd) as total_gross_usd,
SUM(tr.platform_fee) as total_platform_fees,
SUM(tr.net_usd) as total_net_usd,
AVG(tr.token_amount) as avg_redemption_amount,
COUNT(DISTINCT tr.user_id) as unique_redeemers
FROM token_redemptions tr
WHERE tr.status = 'completed'
GROUP BY DATE(tr.created_at)
ORDER BY redemption_date DESC;
-- Sample data for testing (remove in production)
-- INSERT INTO token_purchases (user_id, usd_amount, token_amount, exchange_rate, status) VALUES
-- (1, 10.00, 110, 0.01, 'completed'),
-- (2, 50.00, 575, 0.01, 'completed'),
-- (3, 100.00, 1200, 0.01, 'pending');
COMMIT;

View File

@@ -0,0 +1,111 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
// Load Square configuration
$square_config = require_once __DIR__ . '/config.square.php';
// Include Square SDK
require_once __DIR__ . '/vendor/autoload.php';
use Square\SquareClient;
use Square\Environment;
use Square\Models\CreatePaymentRequest;
use Square\Models\Money;
// Set JSON response header
header('Content-Type: application/json');
// Get POST data
$data = json_decode(file_get_contents('php://input'), true);
if (!$data) {
echo json_encode([
'success' => false,
'message' => 'Invalid request data'
]);
exit;
}
// Validate required fields
if (!isset($data['streamer_id']) || !isset($data['amount'])) {
echo json_encode([
'success' => false,
'message' => 'Missing required fields'
]);
exit;
}
// Validate amount
if ($data['amount'] < $square_config['square']['min_donation'] ||
$data['amount'] > $square_config['square']['max_donation']) {
echo json_encode([
'success' => false,
'message' => 'Invalid donation amount'
]);
exit;
}
try {
// Initialize Square client
$square_client = new SquareClient([
'accessToken' => $square_config['square']['access_token'],
'environment' => $square_config['square']['environment'] === 'production' ? Environment::PRODUCTION : Environment::SANDBOX
]);
// Create payment request
$money = new Money();
$money->setAmount($data['amount'] * 100); // Convert to cents
$money->setCurrency($square_config['square']['currency']);
$payment_request = new CreatePaymentRequest();
$payment_request->setSourceId('EXTERNAL');
$payment_request->setAmountMoney($money);
$payment_request->setLocationId($square_config['square']['location_id']);
// Add metadata
$payment_request->setMetadata([
'streamer_id' => $data['streamer_id'],
'donor_name' => $data['donor_name'] ?? '',
'message' => $data['message'] ?? ''
]);
// Create payment
$payment = $square_client->getPaymentsApi()->createPayment($payment_request);
if ($payment->isSuccess()) {
// Record donation in database
$sql = "INSERT INTO donations (
streamer_id, amount, donor_name, message, payment_id, status, created_at
) VALUES (?, ?, ?, ?, ?, 'pending', NOW())";
$params = [
$data['streamer_id'],
$data['amount'],
$data['donor_name'] ?? '',
$data['message'] ?? '',
$payment->getResult()->getPayment()->getId()
];
$class_database->executeQuery($sql, $params);
// Get payment URL
$payment_url = $payment->getResult()->getPayment()->getPaymentUrl();
echo json_encode([
'success' => true,
'payment_url' => $payment_url,
'message' => 'Payment created successfully'
]);
} else {
echo json_encode([
'success' => false,
'message' => 'Failed to create payment: ' . $payment->getErrors()[0]->getDetail()
]);
}
} catch (Exception $e) {
echo json_encode([
'success' => false,
'message' => 'Error processing donation: ' . $e->getMessage()
]);
}

View File

@@ -0,0 +1,24 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
require_once __DIR__ . '/../config/config.php';
use Donations\DonationHandler;
// Get streamer information
$streamer_id = $_GET['streamer_id'] ?? 0;
$sql = "SELECT username, display_name FROM users WHERE user_id = ?";
$streamer = db()->getRow($sql, [$streamer_id]);
if (!$streamer) {
handle_error('Invalid streamer', 404);
}
// Initialize donation handler
$donation_handler = new DonationHandler();
// Load view
view('donation_form', [
'streamer' => $streamer,
'config' => $square_config
]);

View File

@@ -0,0 +1,69 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
$usr_key = $class_filter->clr_str($_GET['u']);
header('Content-Type: text/html; charset=UTF-8');
?><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Donations Overlay</title>
<style>
html,body{margin:0;height:100%;background:transparent;overflow:hidden}
.ov-wrap{position:relative;width:100%;height:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#fff}
.event{position:absolute;left:16px;bottom:16px;background:rgba(0,0,0,.55);padding:12px 14px;border-radius:8px;backdrop-filter:saturate(150%) blur(6px);box-shadow:0 6px 18px rgba(0,0,0,.3)}
.event .name{font-weight:600}
.event .amount{color:#6cf;margin-left:8px}
.goal{position:absolute;right:16px;top:16px;background:rgba(0,0,0,.55);padding:10px 12px;border-radius:8px}
.bar{width:220px;height:8px;background:rgba(255,255,255,.2);border-radius:6px;margin-top:8px;overflow:hidden}
.fill{height:100%;background:#6cf;width:0%}
</style>
<script>const uKey = <?php echo json_encode($usr_key); ?>;</script>
</head>
<body>
<div class="ov-wrap">
<div class="goal">
<div id="goal-title">Goal</div>
<div class="bar"><div id="goal-fill" class="fill"></div></div>
</div>
<div id="event" class="event" style="display:none"></div>
</div>
<script>
async function tick(){
try{
const res = await fetch('../api/overlay_status.php?u='+encodeURIComponent(uKey), {cache:'no-store'});
const j = await res.json();
const ev = document.getElementById('event');
if (j && j.recent && j.recent[0]){
const r = j.recent[0];
ev.innerHTML = `<span class="name">${r.name || 'Someone'}</span> <span class="amount">$${Number(r.amount||0).toFixed(2)}</span><div>${r.message?r.message:''}</div>`;
ev.style.display='block';
}
const pct = Math.max(0, Math.min(100, j.goal && j.goal.target>0 ? (j.goal.raised/j.goal.target*100) : 0));
document.getElementById('goal-fill').style.width = pct+'%';
document.getElementById('goal-title').textContent = j.goal && j.goal.title ? j.goal.title : 'Goal';
}catch(e){/* noop */}
}
tick(); setInterval(tick, 5000);
</script>
</body>
/html>

View File

@@ -0,0 +1,35 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
require_once __DIR__ . '/../config/config.php';
use Donations\DonationHandler;
// Get POST data
$data = json_decode(file_get_contents('php://input'), true);
if (!$data) {
json_response([
'success' => false,
'message' => 'Invalid request data'
], 400);
}
// Validate required fields
if (!isset($data['streamer_id']) || !isset($data['amount'])) {
json_response([
'success' => false,
'message' => 'Missing required fields'
], 400);
}
// Process donation
$handler = new DonationHandler();
$result = $handler->createDonation(
$data['streamer_id'],
$data['amount'],
$data['donor_name'] ?? '',
$data['message'] ?? ''
);
json_response($result);

View File

@@ -0,0 +1,50 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
require_once __DIR__ . '/../config/config.php';
use Donations\DonationHandler;
// Get JSON input
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
handle_error('Invalid request data', 400);
}
// Validate required fields
$required_fields = ['nonce', 'amount', 'streamer_id'];
foreach ($required_fields as $field) {
if (!isset($input[$field]) || empty($input[$field])) {
handle_error("Missing required field: {$field}", 400);
}
}
// Initialize donation handler
$donation_handler = new DonationHandler();
try {
// Process the donation
$result = $donation_handler->createDonation(
$input['streamer_id'],
$input['amount'],
$input['nonce'],
$input['message'] ?? null
);
// Return success response
echo json_encode([
'success' => true,
'message' => 'Donation processed successfully',
'donation_id' => $result['donation_id']
]);
} catch (Exception $e) {
// Log error
error_log("Donation processing error: " . $e->getMessage());
// Return error response
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}

View File

@@ -0,0 +1,9 @@
<?php
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
require_once __DIR__ . '/../config/config.php';
use Donations\WebhookHandler;
$handler = new WebhookHandler();
$handler->handle();

View File

@@ -0,0 +1,722 @@
<?php
/*******************************************************************************************************************
| Rainforest Pay Donation Form for EasyStream
| User-friendly donation interface with Rainforest Pay integration
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
// Load Rainforest Pay handler
require_once __DIR__ . '/rainforest_pay.php';
$error_message = '';
$success_message = '';
$streamer_id = isset($_GET['streamer']) ? intval($_GET['streamer']) : 0;
if (!$streamer_id) {
$error_message = 'Invalid streamer ID';
}
// Get streamer information
$streamer = null;
if ($streamer_id) {
$sql = "SELECT usr_id, usr_user, usr_dname, ch_title, usr_photo
FROM db_accountuser WHERE usr_id = ? AND active = 1";
$result = $class_database->execute($sql, [$streamer_id]);
$streamer = $result->fields ?? null;
}
if (!$streamer) {
$error_message = 'Streamer not found';
}
// Initialize Rainforest Pay handler
$rainforest = new RainforestPayHandler($class_database);
$payment_methods = $rainforest->getPaymentMethods();
// Handle form submission
if ($_POST && !$error_message) {
$amount = floatval($_POST['amount'] ?? 0);
$donor_name = $class_filter->clr_str($_POST['donor_name'] ?? '');
$message = $class_filter->clr_str($_POST['message'] ?? '');
$payment_method = $class_filter->clr_str($_POST['payment_method'] ?? 'card');
if ($amount <= 0) {
$error_message = 'Please enter a valid donation amount';
} else {
$result = $rainforest->createDonation($streamer_id, $amount, $donor_name, $message, $payment_method);
if ($result['success']) {
// Redirect to payment URL if provided
if (isset($result['payment_url'])) {
header('Location: ' . $result['payment_url']);
exit;
} else {
$success_message = $result['message'];
}
} else {
$error_message = $result['message'];
}
}
}
$config = require __DIR__ . '/config.rainforest.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Donate to <?php echo htmlspecialchars($streamer['ch_title'] ?: $streamer['usr_dname'] ?: $streamer['usr_user']); ?> - EasyStream</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.donation-container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
max-width: 500px;
width: 90%;
overflow: hidden;
}
.donation-header {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
padding: 2rem;
text-align: center;
}
.streamer-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
border: 4px solid white;
margin: 0 auto 1rem;
display: block;
object-fit: cover;
}
.streamer-name {
font-size: 1.5rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
}
.donation-subtitle {
opacity: 0.9;
margin: 0;
}
.donation-form {
padding: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #495057;
}
.form-input {
width: 100%;
padding: 0.75rem;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s ease;
box-sizing: border-box;
}
.form-input:focus {
outline: none;
border-color: #28a745;
box-shadow: 0 0 0 3px rgba(40,167,69,0.1);
}
.amount-buttons {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
margin-bottom: 1rem;
}
.amount-btn {
padding: 0.75rem;
border: 2px solid #e9ecef;
border-radius: 8px;
background: white;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.amount-btn:hover {
border-color: #28a745;
background: #f8fff9;
}
.amount-btn.active {
border-color: #28a745;
background: #28a745;
color: white;
}
.payment-methods {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.5rem;
margin-bottom: 1rem;
}
.payment-method {
padding: 1rem;
border: 2px solid #e9ecef;
border-radius: 8px;
background: white;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.payment-method:hover {
border-color: #28a745;
background: #f8fff9;
}
.payment-method.active {
border-color: #28a745;
background: #28a745;
color: white;
}
.payment-method-icon {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.payment-method-name {
font-weight: 500;
font-size: 0.875rem;
}
.donate-btn {
width: 100%;
padding: 1rem;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.donate-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(40,167,69,0.3);
}
.donate-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
border: 1px solid #f5c6cb;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
border: 1px solid #c3e6cb;
}
.donation-info {
background: #e3f2fd;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.875rem;
color: #1565c0;
}
.back-link {
text-align: center;
margin-top: 1rem;
}
.back-link a {
color: #6c757d;
text-decoration: none;
font-size: 0.875rem;
}
.back-link a:hover {
color: #28a745;
}
@media (max-width: 480px) {
.donation-container {
width: 95%;
margin: 1rem;
}
.donation-form {
padding: 1.5rem;
}
.amount-buttons {
grid-template-columns: repeat(2, 1fr);
}
.payment-methods {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="donation-container">
<div class="donation-header">
<img src="<?php echo $streamer['usr_photo'] ?: '/f_templates/tpl_frontend/img/default-avatar.png'; ?>"
alt="<?php echo htmlspecialchars($streamer['usr_user']); ?>"
class="streamer-avatar">
<h1 class="streamer-name">
<?php echo htmlspecialchars($streamer['ch_title'] ?: $streamer['usr_dname'] ?: $streamer['usr_user']); ?>
</h1>
<p class="donation-subtitle">Support this creator with a donation</p>
</div>
<div class="donation-form">
<?php if ($error_message): ?>
<div class="error-message">
<?php echo htmlspecialchars($error_message); ?>
</div>
<?php endif; ?>
<?php if ($success_message): ?>
<div class="success-message">
<?php echo htmlspecialchars($success_message); ?>
</div>
<?php endif; ?>
<?php if (!$error_message || $streamer): ?>
<div class="donation-info">
💡 Your donation helps support content creation.
Platform fee: <?php echo $config['rainforest']['platform_fee_percentage']; ?>% +
$<?php echo $config['rainforest']['platform_fee_fixed']; ?>
</div>
<form method="POST" id="donation-form">
<!-- Donation Type Selection -->
<div class="form-group">
<label class="form-label">Donation Type</label>
<div class="donation-type-selector">
<button type="button" class="donation-type-btn active" data-type="usd">
💵 USD Donation
</button>
<button type="button" class="donation-type-btn" data-type="tokens">
🪙 Token Donation
</button>
</div>
<input type="hidden" name="donation_type" id="donation_type" value="usd">
</div>
<!-- USD Donation Section -->
<div class="form-group" id="usd-section">
<label class="form-label">Donation Amount (USD)</label>
<div class="amount-buttons">
<button type="button" class="amount-btn" data-amount="5">$5</button>
<button type="button" class="amount-btn" data-amount="10">$10</button>
<button type="button" class="amount-btn" data-amount="25">$25</button>
<button type="button" class="amount-btn" data-amount="50">$50</button>
<button type="button" class="amount-btn" data-amount="100">$100</button>
<button type="button" class="amount-btn" data-amount="custom">Custom</button>
</div>
<input type="number"
name="amount"
id="amount"
class="form-input"
placeholder="Enter amount"
min="<?php echo $config['rainforest']['min_donation']; ?>"
max="<?php echo $config['rainforest']['max_donation']; ?>"
step="0.01"
required>
</div>
<!-- Token Donation Section -->
<div class="form-group" id="token-section" style="display: none;">
<label class="form-label">Token Amount</label>
<div class="token-info">
<img src="/f_templates/tpl_frontend/img/default-token.svg" alt="Token" style="width: 24px; height: 24px; margin-right: 8px;">
<span>Send EasyCoins directly from your balance</span>
</div>
<div class="token-buttons">
<button type="button" class="token-btn" data-tokens="10">10 EC</button>
<button type="button" class="token-btn" data-tokens="25">25 EC</button>
<button type="button" class="token-btn" data-tokens="50">50 EC</button>
<button type="button" class="token-btn" data-tokens="100">100 EC</button>
<button type="button" class="token-btn" data-tokens="250">250 EC</button>
<button type="button" class="token-btn" data-tokens="custom">Custom</button>
</div>
<input type="number"
name="token_amount"
id="token_amount"
class="form-input"
placeholder="Enter token amount"
min="1"
step="1">
<div class="user-balance" id="user-balance" style="margin-top: 10px; font-size: 0.9em; color: #666;">
Your balance: <span id="balance-amount">Loading...</span>
</div>
</div>
<div class="form-group">
<label class="form-label">Your Name (Optional)</label>
<input type="text"
name="donor_name"
class="form-input"
placeholder="Enter your name or stay anonymous">
</div>
<div class="form-group">
<label class="form-label">Message (Optional)</label>
<textarea name="message"
class="form-input"
rows="3"
placeholder="Leave a message for the creator"></textarea>
</div>
<div class="form-group">
<label class="form-label">Payment Method</label>
<div class="payment-methods">
<?php foreach ($payment_methods as $method): ?>
<div class="payment-method" data-method="<?php echo $method['id']; ?>">
<div class="payment-method-icon">
<?php
$icons = [
'card' => '💳',
'bank_transfer' => '🏦',
'mobile_money' => '📱',
'wallet' => '👛'
];
echo $icons[$method['id']] ?? '💳';
?>
</div>
<div class="payment-method-name"><?php echo $method['name']; ?></div>
</div>
<?php endforeach; ?>
</div>
<input type="hidden" name="payment_method" id="payment_method" value="card" required>
</div>
<button type="submit" class="donate-btn" id="donate-btn">
💖 Donate Now
</button>
</form>
<div class="back-link">
<a href="javascript:history.back()">← Back to channel</a>
</div>
<?php endif; ?>
</div>
</div>
<script>
// Amount button handling
document.querySelectorAll('.amount-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.amount-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
const amount = this.dataset.amount;
const amountInput = document.getElementById('amount');
if (amount === 'custom') {
amountInput.focus();
} else {
amountInput.value = amount;
}
updateDonateButton();
});
});
// Payment method handling
document.querySelectorAll('.payment-method').forEach(method => {
method.addEventListener('click', function() {
document.querySelectorAll('.payment-method').forEach(m => m.classList.remove('active'));
this.classList.add('active');
document.getElementById('payment_method').value = this.dataset.method;
});
});
// Set default payment method
document.querySelector('.payment-method').classList.add('active');
// Amount input handling
document.getElementById('amount').addEventListener('input', function() {
document.querySelectorAll('.amount-btn').forEach(b => b.classList.remove('active'));
updateDonateButton();
});
function updateDonateButton() {
const amount = parseFloat(document.getElementById('amount').value) || 0;
const btn = document.getElementById('donate-btn');
if (amount > 0) {
btn.textContent = `💖 Donate $${amount.toFixed(2)}`;
btn.disabled = false;
} else {
btn.textContent = '💖 Donate Now';
btn.disabled = true;
}
}
// Form submission handling
document.getElementById('donation-form').addEventListener('submit', function(e) {
const amount = parseFloat(document.getElementById('amount').value) || 0;
const minAmount = <?php echo $config['rainforest']['min_donation']; ?>;
const maxAmount = <?php echo $config['rainforest']['max_donation']; ?>;
if (amount < minAmount || amount > maxAmount) {
e.preventDefault();
alert(`Donation amount must be between $${minAmount} and $${maxAmount}`);
return;
}
// Show loading state
const btn = document.getElementById('donate-btn');
btn.disabled = true;
btn.textContent = '⏳ Processing...';
});
// Initialize
updateDonateButton();
</script>
</body>
</html>
/* Token System Styles */
.donation-type-selector {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.donation-type-btn {
flex: 1;
padding: 0.75rem;
border: 2px solid #dee2e6;
border-radius: 8px;
background: white;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.donation-type-btn:hover {
border-color: #28a745;
background: #f8fff9;
}
.donation-type-btn.active {
border-color: #28a745;
background: #28a745;
color: white;
}
.token-info {
display: flex;
align-items: center;
background: #f8f9fa;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.9rem;
color: #495057;
}
.token-buttons {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
margin-bottom: 1rem;
}
.token-btn {
padding: 0.75rem;
border: 2px solid #FFD700;
border-radius: 8px;
background: linear-gradient(135deg, #FFD700, #FFA500);
color: white;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.token-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(255,215,0,0.3);
}
.token-btn.active {
border-color: #B8860B;
box-shadow: 0 0 0 3px rgba(255,215,0,0.2);
}
.user-balance {
background: #e3f2fd;
padding: 0.75rem;
border-radius: 8px;
text-align: center;
font-weight: 500;
}
.insufficient-balance {
background: #ffebee;
color: #c62828;
}
@media (max-width: 480px) {
.donation-type-selector {
flex-direction: column;
}
.token-buttons {
grid-template-columns: repeat(2, 1fr);
}
}
// Enhanced donation form JavaScript
// Donation type switching
document.querySelectorAll('.donation-type-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.donation-type-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
const type = this.dataset.type;
document.getElementById('donation_type').value = type;
if (type === 'usd') {
document.getElementById('usd-section').style.display = 'block';
document.getElementById('token-section').style.display = 'none';
document.getElementById('amount').required = true;
document.getElementById('token_amount').required = false;
} else {
document.getElementById('usd-section').style.display = 'none';
document.getElementById('token-section').style.display = 'block';
document.getElementById('amount').required = false;
document.getElementById('token_amount').required = true;
loadUserBalance();
}
});
});
// Token button handling
document.querySelectorAll('.token-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.token-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
const tokens = this.dataset.tokens;
const tokenInput = document.getElementById('token_amount');
if (tokens === 'custom') {
tokenInput.focus();
} else {
tokenInput.value = tokens;
}
checkTokenBalance();
});
});
// Token amount input handling
document.getElementById('token_amount').addEventListener('input', function() {
document.querySelectorAll('.token-btn').forEach(b => b.classList.remove('active'));
checkTokenBalance();
});
function loadUserBalance() {
// Load user's token balance via AJAX
fetch('/api/user/token-balance')
.then(response => response.json())
.then(data => {
const balanceElement = document.getElementById('balance-amount');
if (data.success) {
balanceElement.innerHTML = `<img src="/f_templates/tpl_frontend/img/default-token.svg" style="width: 16px; height: 16px; margin-right: 4px;">${data.balance} EasyCoins`;
} else {
balanceElement.textContent = 'Unable to load balance';
}
})
.catch(error => {
document.getElementById('balance-amount').textContent = 'Error loading balance';
});
}
function checkTokenBalance() {
const tokenAmount = parseInt(document.getElementById('token_amount').value) || 0;
const balanceElement = document.getElementById('user-balance');
const donateBtn = document.getElementById('donate-btn');
// This would check against actual user balance
// For now, we'll assume they have sufficient balance
if (tokenAmount > 0) {
balanceElement.classList.remove('insufficient-balance');
donateBtn.disabled = false;
donateBtn.textContent = `🪙 Donate ${tokenAmount} EasyCoins`;
} else {
donateBtn.textContent = '🪙 Donate Tokens';
donateBtn.disabled = true;
}
}
// Update the main donate button text based on type
function updateDonateButton() {
const donationType = document.getElementById('donation_type').value;
const btn = document.getElementById('donate-btn');
if (donationType === 'tokens') {
const tokenAmount = parseInt(document.getElementById('token_amount').value) || 0;
if (tokenAmount > 0) {
btn.textContent = `🪙 Donate ${tokenAmount} EasyCoins`;
} else {
btn.textContent = '🪙 Donate Tokens';
}
} else {
const amount = parseFloat(document.getElementById('amount').value) || 0;
if (amount > 0) {
btn.textContent = `💖 Donate $${amount.toFixed(2)}`;
} else {
btn.textContent = '💖 Donate Now';
}
}
}

View File

@@ -0,0 +1,813 @@
<?php
/*******************************************************************************************************************
| Rainforest Pay Integration for EasyStream
| Complete payment processing with Rainforest Pay API
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
class RainforestPayHandler {
private $config;
private $class_database;
private $api_base_url;
private $headers;
public function __construct($class_database) {
$this->class_database = $class_database;
$this->config = require __DIR__ . '/config.rainforest.php';
$env = $this->config['rainforest']['environment'];
$this->api_base_url = $this->config['rainforest']['api_base_url'][$env];
$this->headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->config['rainforest']['api_key'],
'X-Merchant-ID: ' . $this->config['rainforest']['merchant_id']
];
}
/**
* Create a donation payment
*/
public function createDonation($streamer_id, $amount, $donor_name = '', $message = '', $payment_method = 'card') {
try {
// Validate amount
if ($amount < $this->config['rainforest']['min_donation'] ||
$amount > $this->config['rainforest']['max_donation']) {
return [
'success' => false,
'message' => 'Invalid donation amount. Must be between $' .
$this->config['rainforest']['min_donation'] . ' and $' .
$this->config['rainforest']['max_donation']
];
}
// Get streamer information
$streamer = $this->getStreamerInfo($streamer_id);
if (!$streamer) {
return ['success' => false, 'message' => 'Streamer not found'];
}
// Calculate fees
$platform_fee = $this->calculatePlatformFee($amount);
$streamer_amount = $amount - $platform_fee;
// Create payment request
$payment_data = [
'amount' => $amount * 100, // Convert to cents
'currency' => $this->config['rainforest']['currency'],
'payment_method' => $payment_method,
'description' => "Donation to {$streamer['username']}",
'metadata' => [
'type' => 'donation',
'streamer_id' => $streamer_id,
'streamer_username' => $streamer['username'],
'donor_name' => $donor_name,
'message' => $message,
'platform_fee' => $platform_fee,
'streamer_amount' => $streamer_amount
],
'webhook_url' => $this->config['rainforest']['webhook_url'],
'return_url' => "https://{$_SERVER['HTTP_HOST']}/donations/success",
'cancel_url' => "https://{$_SERVER['HTTP_HOST']}/donations/cancel"
];
// Make API request
$response = $this->makeApiRequest('POST', '/payments', $payment_data);
if ($response && isset($response['id'])) {
// Store pending donation in database
$donation_id = $this->storePendingDonation([
'streamer_id' => $streamer_id,
'amount' => $amount,
'platform_fee' => $platform_fee,
'streamer_amount' => $streamer_amount,
'donor_name' => $donor_name,
'message' => $message,
'payment_method' => $payment_method,
'rainforest_payment_id' => $response['id'],
'status' => 'pending'
]);
return [
'success' => true,
'payment_id' => $response['id'],
'donation_id' => $donation_id,
'payment_url' => $response['payment_url'] ?? null,
'message' => 'Payment created successfully'
];
} else {
return [
'success' => false,
'message' => 'Failed to create payment: ' . ($response['error'] ?? 'Unknown error')
];
}
} catch (Exception $e) {
$this->logError('Create Donation Error', $e->getMessage());
return [
'success' => false,
'message' => 'Error processing donation: ' . $e->getMessage()
];
}
}
/**
* Create a payment (supports both donations and token purchases)
*/
public function createPayment($payment_data) {
try {
// Make API request
$response = $this->makeApiRequest('POST', '/payments', $payment_data);
if ($response && isset($response['id'])) {
return [
'success' => true,
'payment_id' => $response['id'],
'payment_url' => $response['payment_url'] ?? null,
'message' => 'Payment created successfully'
];
} else {
return [
'success' => false,
'message' => 'Failed to create payment: ' . ($response['error'] ?? 'Unknown error')
];
}
} catch (Exception $e) {
$this->logError('Create Payment Error', $e->getMessage());
return [
'success' => false,
'message' => 'Error creating payment: ' . $e->getMessage()
];
}
}
/**
* Create a payout (for token redemptions)
*/
public function createPayout($payout_data) {
try {
// Make API request
$response = $this->makeApiRequest('POST', '/payouts', $payout_data);
if ($response && isset($response['id'])) {
return [
'success' => true,
'payout_id' => $response['id'],
'message' => 'Payout created successfully'
];
} else {
return [
'success' => false,
'message' => 'Failed to create payout: ' . ($response['error'] ?? 'Unknown error')
];
}
} catch (Exception $e) {
$this->logError('Create Payout Error', $e->getMessage());
return [
'success' => false,
'message' => 'Error creating payout: ' . $e->getMessage()
];
}
}
/**
* Process webhook from Rainforest Pay
*/
public function processWebhook($payload, $signature) {
try {
// Verify webhook signature
if (!$this->verifyWebhookSignature($payload, $signature)) {
return ['success' => false, 'message' => 'Invalid webhook signature'];
}
$data = json_decode($payload, true);
if (!$data) {
return ['success' => false, 'message' => 'Invalid webhook payload'];
}
$event_type = $data['event'] ?? '';
$payment_data = $data['data'] ?? [];
switch ($event_type) {
case 'payment.completed':
return $this->handlePaymentCompleted($payment_data);
case 'payment.failed':
return $this->handlePaymentFailed($payment_data);
case 'payment.cancelled':
return $this->handlePaymentCancelled($payment_data);
case 'payout.completed':
return $this->handlePayoutCompleted($payment_data);
case 'payout.failed':
return $this->handlePayoutFailed($payment_data);
default:
$this->logError('Unknown Webhook Event', $event_type);
return ['success' => false, 'message' => 'Unknown event type'];
}
} catch (Exception $e) {
$this->logError('Webhook Processing Error', $e->getMessage());
return ['success' => false, 'message' => 'Webhook processing failed'];
}
}
/**
* Handle completed payment
*/
private function handlePaymentCompleted($payment_data) {
$payment_id = $payment_data['id'];
$metadata = $payment_data['metadata'] ?? [];
$payment_type = $metadata['type'] ?? 'donation';
try {
if ($payment_type === 'token_purchase') {
return $this->handleTokenPurchaseCompleted($payment_data, $metadata);
} else {
// Handle regular donation
$sql = "UPDATE donations SET
status = 'completed',
completed_at = NOW(),
rainforest_data = ?
WHERE rainforest_payment_id = ?";
$this->class_database->execute($sql, [
json_encode($payment_data),
$payment_id
]);
// Update streamer balance
if (isset($metadata['streamer_id']) && isset($metadata['streamer_amount'])) {
$this->updateStreamerBalance($metadata['streamer_id'], $metadata['streamer_amount']);
}
// Send notification to streamer
$this->sendDonationNotification($metadata);
return ['success' => true, 'message' => 'Payment completed successfully'];
}
} catch (Exception $e) {
$this->logError('Payment Completion Error', $e->getMessage());
return ['success' => false, 'message' => 'Error updating payment status'];
}
}
/**
* Handle completed token purchase
*/
private function handleTokenPurchaseCompleted($payment_data, $metadata) {
$payment_id = $payment_data['id'];
$purchase_id = $metadata['purchase_id'] ?? null;
$user_id = $metadata['user_id'] ?? null;
$token_amount = $metadata['token_amount'] ?? 0;
if (!$purchase_id || !$user_id || !$token_amount) {
return ['success' => false, 'message' => 'Invalid token purchase metadata'];
}
try {
$this->class_database->beginTransaction();
// Update purchase record
$sql = "UPDATE token_purchases SET status = 'completed', updated_at = NOW() WHERE id = ? AND payment_id = ?";
$this->class_database->execute($sql, [$purchase_id, $payment_id]);
// Add tokens to user balance
require_once '../../../f_core/f_classes/class.token.php';
VToken::recordTransaction($user_id, $token_amount, 'purchase', "Token purchase #{$purchase_id}");
$this->class_database->commitTransaction();
$this->logInfo('Token Purchase Completed', "Purchase ID: {$purchase_id}, User: {$user_id}, Tokens: {$token_amount}");
return ['success' => true, 'message' => 'Token purchase completed successfully'];
} catch (Exception $e) {
$this->class_database->rollbackTransaction();
$this->logError('Token Purchase Completion Error', $e->getMessage());
return ['success' => false, 'message' => 'Error completing token purchase'];
}
}
/**
* Handle failed payment
*/
private function handlePaymentFailed($payment_data) {
$payment_id = $payment_data['id'];
$sql = "UPDATE donations SET
status = 'failed',
failed_at = NOW(),
failure_reason = ?,
rainforest_data = ?
WHERE rainforest_payment_id = ?";
$this->class_database->execute($sql, [
$payment_data['failure_reason'] ?? 'Payment failed',
json_encode($payment_data),
$payment_id
]);
return ['success' => true, 'message' => 'Payment failure recorded'];
}
/**
* Handle completed payout
*/
private function handlePayoutCompleted($payout_data) {
$payout_id = $payout_data['id'];
$metadata = $payout_data['metadata'] ?? [];
$payout_type = $metadata['type'] ?? 'streamer_payout';
try {
if ($payout_type === 'token_redemption') {
return $this->handleTokenRedemptionCompleted($payout_data, $metadata);
} else {
// Handle regular streamer payout
$sql = "UPDATE payouts SET
status = 'completed',
completed_at = NOW(),
rainforest_data = ?
WHERE rainforest_payout_id = ?";
$this->class_database->execute($sql, [
json_encode($payout_data),
$payout_id
]);
return ['success' => true, 'message' => 'Payout completed successfully'];
}
} catch (Exception $e) {
$this->logError('Payout Completion Error', $e->getMessage());
return ['success' => false, 'message' => 'Error updating payout status'];
}
}
/**
* Handle failed payout
*/
private function handlePayoutFailed($payout_data) {
$payout_id = $payout_data['id'];
$metadata = $payout_data['metadata'] ?? [];
$payout_type = $metadata['type'] ?? 'streamer_payout';
try {
if ($payout_type === 'token_redemption') {
return $this->handleTokenRedemptionFailed($payout_data, $metadata);
} else {
// Handle regular streamer payout failure
$sql = "UPDATE payouts SET
status = 'failed',
failed_at = NOW(),
failure_reason = ?,
rainforest_data = ?
WHERE rainforest_payout_id = ?";
$this->class_database->execute($sql, [
$payout_data['failure_reason'] ?? 'Payout failed',
json_encode($payout_data),
$payout_id
]);
return ['success' => true, 'message' => 'Payout failure recorded'];
}
} catch (Exception $e) {
$this->logError('Payout Failure Error', $e->getMessage());
return ['success' => false, 'message' => 'Error updating payout status'];
}
}
/**
* Handle completed token redemption
*/
private function handleTokenRedemptionCompleted($payout_data, $metadata) {
$payout_id = $payout_data['id'];
$redemption_id = $metadata['redemption_id'] ?? null;
$user_id = $metadata['user_id'] ?? null;
if (!$redemption_id || !$user_id) {
return ['success' => false, 'message' => 'Invalid token redemption metadata'];
}
try {
$this->class_database->beginTransaction();
// Update payout record
$sql = "UPDATE token_payouts SET status = 'completed', completed_at = NOW() WHERE rainforest_payout_id = ?";
$this->class_database->execute($sql, [$payout_id]);
// Update redemption record
$sql = "UPDATE token_redemptions SET status = 'completed', completed_at = NOW() WHERE id = ?";
$this->class_database->execute($sql, [$redemption_id]);
$this->class_database->commitTransaction();
$this->logInfo('Token Redemption Completed', "Redemption ID: {$redemption_id}, User: {$user_id}");
return ['success' => true, 'message' => 'Token redemption completed successfully'];
} catch (Exception $e) {
$this->class_database->rollbackTransaction();
$this->logError('Token Redemption Completion Error', $e->getMessage());
return ['success' => false, 'message' => 'Error completing token redemption'];
}
}
/**
* Handle failed token redemption
*/
private function handleTokenRedemptionFailed($payout_data, $metadata) {
$payout_id = $payout_data['id'];
$redemption_id = $metadata['redemption_id'] ?? null;
$user_id = $metadata['user_id'] ?? null;
$token_amount = $metadata['token_amount'] ?? 0;
if (!$redemption_id || !$user_id || !$token_amount) {
return ['success' => false, 'message' => 'Invalid token redemption metadata'];
}
try {
$this->class_database->beginTransaction();
// Update payout record
$sql = "UPDATE token_payouts SET status = 'failed', failed_at = NOW(), failure_reason = ? WHERE rainforest_payout_id = ?";
$this->class_database->execute($sql, [
$payout_data['failure_reason'] ?? 'Payout failed',
$payout_id
]);
// Update redemption record
$sql = "UPDATE token_redemptions SET status = 'failed', failed_at = NOW() WHERE id = ?";
$this->class_database->execute($sql, [$redemption_id]);
// Refund tokens to user
require_once '../../../f_core/f_classes/class.token.php';
VToken::recordTransaction($user_id, $token_amount, 'refund', "Redemption refund #{$redemption_id}");
$this->class_database->commitTransaction();
$this->logInfo('Token Redemption Failed - Refunded', "Redemption ID: {$redemption_id}, User: {$user_id}, Tokens: {$token_amount}");
return ['success' => true, 'message' => 'Token redemption failed - tokens refunded'];
} catch (Exception $e) {
$this->class_database->rollbackTransaction();
$this->logError('Token Redemption Failure Error', $e->getMessage());
return ['success' => false, 'message' => 'Error handling token redemption failure'];
}
}
/**
* Request payout for streamer
*/
public function requestPayout($streamer_id) {
try {
$balance = $this->getStreamerBalance($streamer_id);
if ($balance < $this->config['rainforest']['min_payout']) {
return [
'success' => false,
'message' => 'Insufficient balance for payout. Minimum: $' .
$this->config['rainforest']['min_payout']
];
}
// Get streamer payout details
$streamer = $this->getStreamerPayoutInfo($streamer_id);
if (!$streamer || !$streamer['payout_method']) {
return [
'success' => false,
'message' => 'Payout method not configured. Please update your payout settings.'
];
}
// Calculate payout amount after fees
$fee_amount = $this->calculatePayoutFee($balance);
$payout_amount = $balance - $fee_amount;
// Create payout request
$payout_data = [
'amount' => $payout_amount * 100, // Convert to cents
'currency' => $this->config['rainforest']['currency'],
'recipient' => [
'type' => $streamer['payout_method'],
'details' => json_decode($streamer['payout_details'], true)
],
'description' => "Payout to {$streamer['username']}",
'metadata' => [
'type' => 'streamer_payout',
'streamer_id' => $streamer_id,
'original_balance' => $balance,
'fee_amount' => $fee_amount,
'payout_amount' => $payout_amount
]
];
$response = $this->makeApiRequest('POST', '/payouts', $payout_data);
if ($response && isset($response['id'])) {
// Record payout request
$this->recordPayoutRequest($streamer_id, $payout_amount, $fee_amount, $response['id']);
return [
'success' => true,
'payout_id' => $response['id'],
'amount' => $payout_amount,
'fee' => $fee_amount,
'message' => 'Payout request submitted successfully'
];
} else {
return [
'success' => false,
'message' => 'Failed to create payout: ' . ($response['error'] ?? 'Unknown error')
];
}
} catch (Exception $e) {
$this->logError('Payout Request Error', $e->getMessage());
return [
'success' => false,
'message' => 'Error processing payout: ' . $e->getMessage()
];
}
}
/**
* Get available payment methods
*/
public function getPaymentMethods() {
$methods = [];
$config_methods = $this->config['rainforest']['payment_methods'];
if ($config_methods['card']) {
$methods[] = [
'id' => 'card',
'name' => 'Credit/Debit Card',
'icon' => 'credit-card',
'description' => 'Visa, Mastercard, American Express'
];
}
if ($config_methods['bank_transfer']) {
$methods[] = [
'id' => 'bank_transfer',
'name' => 'Bank Transfer',
'icon' => 'bank',
'description' => 'Direct bank transfer'
];
}
if ($config_methods['mobile_money']) {
$methods[] = [
'id' => 'mobile_money',
'name' => 'Mobile Money',
'icon' => 'mobile',
'description' => 'MTN, Airtel, Vodafone'
];
}
if ($config_methods['wallet']) {
$methods[] = [
'id' => 'wallet',
'name' => 'Digital Wallet',
'icon' => 'wallet',
'description' => 'PayPal, Apple Pay, Google Pay'
];
}
return $methods;
}
/**
* Get donation history for streamer
*/
public function getDonationHistory($streamer_id, $limit = 20, $offset = 0) {
$sql = "SELECT d.*, u.usr_user as donor_username
FROM donations d
LEFT JOIN db_accountuser u ON d.donor_id = u.usr_id
WHERE d.streamer_id = ? AND d.status = 'completed'
ORDER BY d.completed_at DESC
LIMIT ? OFFSET ?";
return $this->class_database->getRows($sql, [$streamer_id, $limit, $offset]);
}
/**
* Get streamer earnings summary
*/
public function getStreamerEarnings($streamer_id, $period = '30_days') {
$date_condition = $this->getDateCondition($period);
$sql = "SELECT
COUNT(*) as total_donations,
SUM(amount) as total_amount,
SUM(streamer_amount) as total_earned,
SUM(platform_fee) as total_fees,
AVG(amount) as average_donation
FROM donations
WHERE streamer_id = ? AND status = 'completed' AND $date_condition";
return $this->class_database->getRow($sql, [$streamer_id]);
}
// Helper Methods
private function makeApiRequest($method, $endpoint, $data = null) {
$url = $this->api_base_url . $endpoint;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $this->headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true
]);
if ($method === 'POST' && $data) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
throw new Exception('API request failed');
}
$decoded = json_decode($response, true);
if ($http_code >= 400) {
$this->logError('API Error', "HTTP $http_code: " . ($decoded['message'] ?? $response));
return ['error' => $decoded['message'] ?? 'API request failed'];
}
return $decoded;
}
private function verifyWebhookSignature($payload, $signature) {
$expected = hash_hmac('sha256', $payload, $this->config['rainforest']['webhook_secret']);
return hash_equals($expected, $signature);
}
private function calculatePlatformFee($amount) {
$percentage_fee = $amount * ($this->config['rainforest']['platform_fee_percentage'] / 100);
$fixed_fee = $this->config['rainforest']['platform_fee_fixed'];
return $percentage_fee + $fixed_fee;
}
private function calculatePayoutFee($amount) {
$percentage_fee = $amount * ($this->config['rainforest']['payout_fee_percentage'] / 100);
$fixed_fee = $this->config['rainforest']['payout_fee_fixed'];
return $percentage_fee + $fixed_fee;
}
private function getStreamerInfo($streamer_id) {
$sql = "SELECT usr_id, usr_user as username, usr_dname as display_name
FROM db_accountuser WHERE usr_id = ?";
return $this->class_database->getRow($sql, [$streamer_id]);
}
private function getStreamerBalance($streamer_id) {
$sql = "SELECT COALESCE(SUM(streamer_amount), 0) as balance
FROM donations
WHERE streamer_id = ? AND status = 'completed' AND payout_id IS NULL";
$result = $this->class_database->getRow($sql, [$streamer_id]);
return $result['balance'] ?? 0;
}
private function updateStreamerBalance($streamer_id, $amount) {
// Balance is calculated dynamically, but we can update a cached value if needed
$sql = "UPDATE db_accountuser SET
donation_balance = COALESCE(donation_balance, 0) + ?
WHERE usr_id = ?";
$this->class_database->execute($sql, [$amount, $streamer_id]);
}
private function storePendingDonation($data) {
$sql = "INSERT INTO donations (
streamer_id, amount, platform_fee, streamer_amount, donor_name,
message, payment_method, rainforest_payment_id, status, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
$this->class_database->execute($sql, [
$data['streamer_id'], $data['amount'], $data['platform_fee'],
$data['streamer_amount'], $data['donor_name'], $data['message'],
$data['payment_method'], $data['rainforest_payment_id'], $data['status']
]);
return $this->class_database->lastInsertId();
}
private function recordPayoutRequest($streamer_id, $amount, $fee, $payout_id) {
$sql = "INSERT INTO payouts (
streamer_id, amount, fee, rainforest_payout_id, status, created_at
) VALUES (?, ?, ?, ?, 'pending', NOW())";
$this->class_database->execute($sql, [$streamer_id, $amount, $fee, $payout_id]);
// Mark donations as paid out
$sql = "UPDATE donations SET payout_id = ?
WHERE streamer_id = ? AND status = 'completed' AND payout_id IS NULL";
$this->class_database->execute($sql, [$payout_id, $streamer_id]);
}
private function logError($type, $message) {
error_log(date('Y-m-d H:i:s') . " [RainforestPay] ERROR - $type: $message\n", 3, 'logs/rainforest_pay.log');
}
private function logInfo($type, $message) {
error_log(date('Y-m-d H:i:s') . " [RainforestPay] INFO - $type: $message\n", 3, 'logs/rainforest_pay.log');
}
private function sendDonationNotification($metadata) {
// Integration with EasyStream notification system
// This would trigger notifications to the streamer about new donations
}
private function getDateCondition($period) {
switch ($period) {
case '7_days':
return "completed_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
case '30_days':
return "completed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
case '90_days':
return "completed_at >= DATE_SUB(NOW(), INTERVAL 90 DAY)";
case '1_year':
return "completed_at >= DATE_SUB(NOW(), INTERVAL 1 YEAR)";
default:
return "completed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
}
}
}
// Initialize handler if accessed directly
if (basename(__FILE__) == basename($_SERVER['SCRIPT_NAME'])) {
$handler = new RainforestPayHandler($class_database);
// Handle different actions
$action = $_GET['action'] ?? $_POST['action'] ?? '';
switch ($action) {
case 'create_donation':
$result = $handler->createDonation(
$_POST['streamer_id'],
floatval($_POST['amount']),
$_POST['donor_name'] ?? '',
$_POST['message'] ?? '',
$_POST['payment_method'] ?? 'card'
);
header('Content-Type: application/json');
echo json_encode($result);
break;
case 'webhook':
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RAINFOREST_SIGNATURE'] ?? '';
$result = $handler->processWebhook($payload, $signature);
header('Content-Type: application/json');
echo json_encode($result);
break;
case 'request_payout':
$result = $handler->requestPayout($_POST['streamer_id']);
header('Content-Type: application/json');
echo json_encode($result);
break;
case 'get_payment_methods':
$methods = $handler->getPaymentMethods();
header('Content-Type: application/json');
echo json_encode(['success' => true, 'methods' => $methods]);
break;
case 'create_payment':
$result = $handler->createPayment($_POST);
header('Content-Type: application/json');
echo json_encode($result);
break;
case 'create_payout':
$result = $handler->createPayout($_POST);
header('Content-Type: application/json');
echo json_encode($result);
break;
default:
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Invalid action']);
}
}
?>

View File

@@ -0,0 +1,55 @@
<?php
/*******************************************************************************************************************
| Rainforest Pay Webhook Handler for EasyStream
| Secure webhook processing for payment notifications
|*******************************************************************************************************************/
define('_ISVALID', true);
include_once '../../../f_core/config.core.php';
// Load Rainforest Pay handler
require_once __DIR__ . '/rainforest_pay.php';
// Set response headers
header('Content-Type: application/json');
try {
// Get webhook payload
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RAINFOREST_SIGNATURE'] ?? '';
// Log webhook received
error_log(date('Y-m-d H:i:s') . " [RainforestPay] Webhook received\n", 3, 'logs/rainforest_webhooks.log');
if (empty($payload)) {
http_response_code(400);
echo json_encode(['error' => 'Empty payload']);
exit;
}
if (empty($signature)) {
http_response_code(400);
echo json_encode(['error' => 'Missing signature']);
exit;
}
// Initialize handler and process webhook
$handler = new RainforestPayHandler($class_database);
$result = $handler->processWebhook($payload, $signature);
if ($result['success']) {
http_response_code(200);
echo json_encode(['status' => 'success', 'message' => $result['message']]);
} else {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => $result['message']]);
}
} catch (Exception $e) {
// Log error
error_log(date('Y-m-d H:i:s') . " [RainforestPay] Webhook error: " . $e->getMessage() . "\n", 3, 'logs/rainforest_webhooks.log');
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Internal server error']);
}
?>

View File

@@ -0,0 +1,26 @@
-- Create API Keys Table
CREATE TABLE api_keys (
key_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
api_key VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
last_used TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- Create API Rate Limiting Table
CREATE TABLE api_rate_limits (
rate_limit_id INT PRIMARY KEY AUTO_INCREMENT,
api_key VARCHAR(64) NOT NULL,
request_count INT DEFAULT 1,
window_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (api_key) REFERENCES api_keys(api_key)
);
-- Create Indexes
CREATE INDEX idx_api_key_active ON api_keys(api_key, is_active);
CREATE INDEX idx_rate_limit_window ON api_rate_limits(api_key, window_start);

View File

@@ -0,0 +1,59 @@
-- Donation Goals Table
CREATE TABLE donation_goals (
goal_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
target_amount DECIMAL(10,2) NOT NULL,
current_amount DECIMAL(10,2) DEFAULT 0.00,
start_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
end_date TIMESTAMP NULL,
status ENUM('active', 'completed', 'cancelled') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id)
);
-- Donation Milestones Table
CREATE TABLE donation_milestones (
milestone_id INT PRIMARY KEY AUTO_INCREMENT,
goal_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
target_amount DECIMAL(10,2) NOT NULL,
reward_description TEXT,
is_achieved BOOLEAN DEFAULT FALSE,
achieved_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (goal_id) REFERENCES donation_goals(goal_id)
);
-- Donation Analytics Table
CREATE TABLE donation_analytics (
analytics_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
date DATE NOT NULL,
total_donations INT DEFAULT 0,
total_amount DECIMAL(10,2) DEFAULT 0.00,
average_donation DECIMAL(10,2) DEFAULT 0.00,
unique_donors INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id),
UNIQUE KEY unique_streamer_date (streamer_id, date)
);
-- Donation Notifications Table
CREATE TABLE donation_notifications (
notification_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
donor_id INT,
type ENUM('donation', 'goal_created', 'goal_completed', 'milestone_achieved') NOT NULL,
title VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id),
FOREIGN KEY (donor_id) REFERENCES users(user_id)
);

View File

@@ -0,0 +1,143 @@
-- Create tables
CREATE TABLE IF NOT EXISTS donations (
donation_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
donor_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
payment_id VARCHAR(255) NOT NULL,
status ENUM('pending', 'completed', 'failed', 'refunded') DEFAULT 'pending',
message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id),
FOREIGN KEY (donor_id) REFERENCES users(user_id)
);
CREATE TABLE IF NOT EXISTS donation_goals (
goal_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
target_amount DECIMAL(10,2) NOT NULL,
current_amount DECIMAL(10,2) DEFAULT 0.00,
start_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
end_date TIMESTAMP NULL,
status ENUM('active', 'completed', 'cancelled') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id)
);
CREATE TABLE IF NOT EXISTS donation_milestones (
milestone_id INT PRIMARY KEY AUTO_INCREMENT,
goal_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
target_amount DECIMAL(10,2) NOT NULL,
reward_description TEXT,
is_completed BOOLEAN DEFAULT FALSE,
completed_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (goal_id) REFERENCES donation_goals(goal_id)
);
CREATE TABLE IF NOT EXISTS donation_analytics (
analytics_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
date DATE NOT NULL,
total_donations INT DEFAULT 0,
total_amount DECIMAL(10,2) DEFAULT 0.00,
average_donation DECIMAL(10,2) DEFAULT 0.00,
unique_donors INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id),
UNIQUE KEY unique_streamer_date (streamer_id, date)
);
CREATE TABLE IF NOT EXISTS donation_notifications (
notification_id INT PRIMARY KEY AUTO_INCREMENT,
streamer_id INT NOT NULL,
type ENUM('donation', 'goal', 'milestone', 'payout') NOT NULL,
title VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
read_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (streamer_id) REFERENCES users(user_id)
);
CREATE TABLE IF NOT EXISTS api_keys (
key_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
api_key VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
last_used TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
CREATE TABLE IF NOT EXISTS api_rate_limits (
rate_limit_id INT PRIMARY KEY AUTO_INCREMENT,
api_key VARCHAR(64) NOT NULL,
request_count INT DEFAULT 1,
window_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (api_key) REFERENCES api_keys(api_key)
);
-- Create indexes
CREATE INDEX idx_donations_streamer ON donations(streamer_id);
CREATE INDEX idx_donations_donor ON donations(donor_id);
CREATE INDEX idx_donations_status ON donations(status);
CREATE INDEX idx_goals_streamer ON donation_goals(streamer_id);
CREATE INDEX idx_goals_status ON donation_goals(status);
CREATE INDEX idx_milestones_goal ON donation_milestones(goal_id);
CREATE INDEX idx_analytics_streamer_date ON donation_analytics(streamer_id, date);
CREATE INDEX idx_notifications_streamer ON donation_notifications(streamer_id);
CREATE INDEX idx_notifications_read ON donation_notifications(is_read);
CREATE INDEX idx_api_key_active ON api_keys(api_key, is_active);
CREATE INDEX idx_rate_limit_window ON api_rate_limits(api_key, window_start);
-- Create views
CREATE OR REPLACE VIEW v_streamer_donations AS
SELECT d.*, u.username as donor_username, u.display_name as donor_display_name
FROM donations d JOIN users u ON d.donor_id = u.user_id;
CREATE OR REPLACE VIEW v_streamer_goals AS
SELECT g.*,
COUNT(m.milestone_id) as milestone_count,
SUM(CASE WHEN m.is_completed = 1 THEN 1 ELSE 0 END) as completed_milestones
FROM donation_goals g
LEFT JOIN donation_milestones m ON g.goal_id = m.goal_id
GROUP BY g.goal_id;
-- Create triggers
DELIMITER //
CREATE TRIGGER tr_update_goal_amount
AFTER INSERT ON donations
FOR EACH ROW
BEGIN
UPDATE donation_goals
SET current_amount = current_amount + NEW.amount
WHERE streamer_id = NEW.streamer_id
AND status = 'active'
AND (end_date IS NULL OR end_date > NOW());
END//
CREATE TRIGGER tr_check_milestone_completion
AFTER UPDATE ON donation_goals
FOR EACH ROW
BEGIN
UPDATE donation_milestones
SET is_completed = 1, completed_at = NOW()
WHERE goal_id = NEW.goal_id
AND is_completed = 0
AND target_amount <= NEW.current_amount;
END//
DELIMITER ;

View File

@@ -0,0 +1,110 @@
<?php
namespace Donations;
class AnalyticsHandler {
private $db;
public function __construct() {
$this->db = db();
}
/**
* Update analytics for a new donation
*/
public function updateAnalytics($streamer_id, $amount, $donor_id) {
$date = date('Y-m-d');
// Get or create analytics record for today
$sql = "INSERT INTO donation_analytics
(streamer_id, date, total_donations, total_amount, unique_donors)
VALUES (?, ?, 1, ?, 1)
ON DUPLICATE KEY UPDATE
total_donations = total_donations + 1,
total_amount = total_amount + ?,
unique_donors = (
SELECT COUNT(DISTINCT donor_id)
FROM donations
WHERE streamer_id = ? AND DATE(created_at) = ?
)";
$this->db->query($sql, [$streamer_id, $date, $amount, $amount, $streamer_id, $date]);
// Update average donation
$sql = "UPDATE donation_analytics
SET average_donation = total_amount / total_donations
WHERE streamer_id = ? AND date = ?";
$this->db->query($sql, [$streamer_id, $date]);
}
/**
* Get analytics for a specific period
*/
public function getAnalytics($streamer_id, $start_date, $end_date) {
$sql = "SELECT
date,
total_donations,
total_amount,
average_donation,
unique_donors
FROM donation_analytics
WHERE streamer_id = ?
AND date BETWEEN ? AND ?
ORDER BY date DESC";
return $this->db->getRows($sql, [$streamer_id, $start_date, $end_date]);
}
/**
* Get summary statistics
*/
public function getSummary($streamer_id) {
$sql = "SELECT
COUNT(*) as total_donations,
SUM(amount) as total_amount,
AVG(amount) as average_donation,
COUNT(DISTINCT donor_id) as unique_donors,
MAX(amount) as largest_donation,
MIN(amount) as smallest_donation
FROM donations
WHERE streamer_id = ?";
return $this->db->getRow($sql, [$streamer_id]);
}
/**
* Get top donors
*/
public function getTopDonors($streamer_id, $limit = 10) {
$sql = "SELECT
u.username,
u.display_name,
COUNT(*) as donation_count,
SUM(d.amount) as total_amount
FROM donations d
JOIN users u ON d.donor_id = u.user_id
WHERE d.streamer_id = ?
GROUP BY d.donor_id
ORDER BY total_amount DESC
LIMIT ?";
return $this->db->getRows($sql, [$streamer_id, $limit]);
}
/**
* Get donation trends
*/
public function getTrends($streamer_id, $days = 30) {
$sql = "SELECT
DATE(created_at) as date,
COUNT(*) as count,
SUM(amount) as total
FROM donations
WHERE streamer_id = ?
AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
GROUP BY DATE(created_at)
ORDER BY date";
return $this->db->getRows($sql, [$streamer_id, $days]);
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Donations;
class ApiKeyManager {
private $db;
private $rate_limit = 100; // requests per minute
private $rate_window = 60; // seconds
public function __construct() {
$this->db = db();
}
/**
* Generate a new API key
*/
public function generateKey($user_id, $name, $description = '') {
$api_key = bin2hex(random_bytes(32));
$sql = "INSERT INTO api_keys (user_id, api_key, name, description)
VALUES (?, ?, ?, ?)";
$this->db->query($sql, [$user_id, $api_key, $name, $description]);
return $api_key;
}
/**
* Validate an API key
*/
public function validateKey($api_key) {
$sql = "SELECT user_id FROM api_keys
WHERE api_key = ? AND is_active = 1";
$result = $this->db->getRow($sql, [$api_key]);
if ($result) {
$this->updateLastUsed($api_key);
return $result['user_id'];
}
return false;
}
/**
* Update last used timestamp
*/
private function updateLastUsed($api_key) {
$sql = "UPDATE api_keys
SET last_used = CURRENT_TIMESTAMP
WHERE api_key = ?";
$this->db->query($sql, [$api_key]);
}
/**
* Check rate limit
*/
public function checkRateLimit($api_key) {
$sql = "SELECT request_count, window_start
FROM api_rate_limits
WHERE api_key = ?
ORDER BY window_start DESC
LIMIT 1";
$result = $this->db->getRow($sql, [$api_key]);
if (!$result) {
$this->createRateLimitWindow($api_key);
return true;
}
$window_start = strtotime($result['window_start']);
$current_time = time();
// If window has expired, create new window
if ($current_time - $window_start >= $this->rate_window) {
$this->createRateLimitWindow($api_key);
return true;
}
// Check if rate limit exceeded
if ($result['request_count'] >= $this->rate_limit) {
return false;
}
// Increment request count
$sql = "UPDATE api_rate_limits
SET request_count = request_count + 1
WHERE api_key = ? AND window_start = ?";
$this->db->query($sql, [$api_key, $result['window_start']]);
return true;
}
/**
* Create new rate limit window
*/
private function createRateLimitWindow($api_key) {
$sql = "INSERT INTO api_rate_limits (api_key, request_count, window_start)
VALUES (?, 1, CURRENT_TIMESTAMP)";
$this->db->query($sql, [$api_key]);
}
/**
* Get user's API keys
*/
public function getUserKeys($user_id) {
$sql = "SELECT key_id, api_key, name, description, is_active,
last_used, created_at
FROM api_keys
WHERE user_id = ?
ORDER BY created_at DESC";
return $this->db->getRows($sql, [$user_id]);
}
/**
* Deactivate API key
*/
public function deactivateKey($key_id, $user_id) {
$sql = "UPDATE api_keys
SET is_active = 0
WHERE key_id = ? AND user_id = ?";
return $this->db->query($sql, [$key_id, $user_id]);
}
/**
* Reactivate API key
*/
public function reactivateKey($key_id, $user_id) {
$sql = "UPDATE api_keys
SET is_active = 1
WHERE key_id = ? AND user_id = ?";
return $this->db->query($sql, [$key_id, $user_id]);
}
/**
* Delete API key
*/
public function deleteKey($key_id, $user_id) {
$sql = "DELETE FROM api_keys
WHERE key_id = ? AND user_id = ?";
return $this->db->query($sql, [$key_id, $user_id]);
}
/**
* Clean up old rate limit records
*/
public function cleanupRateLimits() {
$sql = "DELETE FROM api_rate_limits
WHERE window_start < DATE_SUB(NOW(), INTERVAL 1 HOUR)";
return $this->db->query($sql);
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Donations\Core;
class Api {
protected $config;
protected $db;
protected $logger;
protected $request;
protected $response;
public function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->db = db();
$this->logger = new Logger();
$this->request = $this->parseRequest();
$this->response = [
'success' => false,
'message' => '',
'data' => null
];
}
protected function parseRequest() {
$request = [
'method' => $_SERVER['REQUEST_METHOD'],
'path' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),
'query' => $_GET,
'body' => json_decode(file_get_contents('php://input'), true) ?? [],
'headers' => getallheaders()
];
return $request;
}
protected function validateApiKey() {
$apiKey = $this->request['headers']['X-API-Key'] ?? null;
if (!$apiKey) {
$this->error('API key is required', 401);
return false;
}
$sql = "SELECT * FROM api_keys WHERE api_key = ? AND is_active = 1";
$key = $this->db->fetch($sql, [$apiKey]);
if (!$key) {
$this->error('Invalid API key', 401);
return false;
}
if (!$this->checkRateLimit($key['id'])) {
$this->error('Rate limit exceeded', 429);
return false;
}
return true;
}
protected function checkRateLimit($apiKeyId) {
$timeframe = $this->config['api']['rate_limit']['timeframe'];
$maxRequests = $this->config['api']['rate_limit']['max_requests'];
$sql = "SELECT COUNT(*) as count FROM api_rate_limits
WHERE api_key_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL ? SECOND)";
$result = $this->db->fetch($sql, [$apiKeyId, $timeframe]);
if ($result['count'] >= $maxRequests) {
return false;
}
$sql = "INSERT INTO api_rate_limits (api_key_id) VALUES (?)";
$this->db->execute($sql, [$apiKeyId]);
return true;
}
protected function success($message, $data = null) {
$this->response = [
'success' => true,
'message' => $message,
'data' => $data
];
$this->sendResponse();
}
protected function error($message, $code = 400) {
$this->response = [
'success' => false,
'message' => $message,
'data' => null
];
$this->sendResponse($code);
}
protected function sendResponse($code = 200) {
http_response_code($code);
header('Content-Type: application/json');
echo json_encode($this->response);
exit;
}
protected function validateInput($data, $rules) {
$errors = [];
foreach ($rules as $field => $rule) {
if (!isset($data[$field]) && strpos($rule, 'required') !== false) {
$errors[$field] = "The $field field is required.";
continue;
}
if (isset($data[$field])) {
if (strpos($rule, 'numeric') !== false && !is_numeric($data[$field])) {
$errors[$field] = "The $field must be a number.";
}
if (strpos($rule, 'min:') !== false) {
$min = substr($rule, strpos($rule, 'min:') + 4);
if ($data[$field] < $min) {
$errors[$field] = "The $field must be at least $min.";
}
}
if (strpos($rule, 'max:') !== false) {
$max = substr($rule, strpos($rule, 'max:') + 4);
if ($data[$field] > $max) {
$errors[$field] = "The $field must not be greater than $max.";
}
}
}
}
if (!empty($errors)) {
$this->error('Validation failed', 422);
}
return true;
}
protected function sanitizeInput($input) {
if (is_array($input)) {
return array_map([$this, 'sanitizeInput'], $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Donations\Core;
class Cache {
private static $instance = null;
private $config;
private $cache;
private function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->connect();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function connect() {
try {
$this->cache = new \Memcached();
$this->cache->addServer(
$this->config['cache']['host'],
$this->config['cache']['port']
);
} catch (\Exception $e) {
throw new \Exception("Cache connection failed: " . $e->getMessage());
}
}
public function get($key) {
$value = $this->cache->get($key);
return $value !== false ? $value : null;
}
public function set($key, $value, $ttl = 3600) {
return $this->cache->set($key, $value, time() + $ttl);
}
public function delete($key) {
return $this->cache->delete($key);
}
public function increment($key, $value = 1) {
return $this->cache->increment($key, $value);
}
public function decrement($key, $value = 1) {
return $this->cache->decrement($key, $value);
}
public function flush() {
return $this->cache->flush();
}
public function getMulti($keys) {
return $this->cache->getMulti($keys);
}
public function setMulti($items, $ttl = 3600) {
return $this->cache->setMulti($items, time() + $ttl);
}
public function deleteMulti($keys) {
return $this->cache->deleteMulti($keys);
}
public function remember($key, $ttl, $callback) {
$value = $this->get($key);
if ($value !== null) {
return $value;
}
$value = $callback();
$this->set($key, $value, $ttl);
return $value;
}
public function rememberForever($key, $callback) {
return $this->remember($key, 0, $callback);
}
public function has($key) {
return $this->get($key) !== null;
}
public function missing($key) {
return !$this->has($key);
}
public function pull($key) {
$value = $this->get($key);
$this->delete($key);
return $value;
}
public function put($key, $value, $ttl = 3600) {
return $this->set($key, $value, $ttl);
}
public function add($key, $value, $ttl = 3600) {
return $this->cache->add($key, $value, time() + $ttl);
}
public function forever($key, $value) {
return $this->set($key, $value, 0);
}
public function forget($key) {
return $this->delete($key);
}
public function tags($names) {
return new TaggedCache($this, (array) $names);
}
public function flushTags($names) {
foreach ((array) $names as $name) {
$this->delete("tag:{$name}:keys");
}
}
public function getConnection() {
return $this->cache;
}
public function __destruct() {
$this->cache = null;
}
private function __clone() {}
private function __wakeup() {}
}
class TaggedCache {
private $cache;
private $names;
public function __construct($cache, $names) {
$this->cache = $cache;
$this->names = $names;
}
public function get($key) {
return $this->cache->get($this->taggedItemKey($key));
}
public function put($key, $value, $ttl = 3600) {
$this->cache->set($this->taggedItemKey($key), $value, $ttl);
$this->pushKey($key);
}
public function remember($key, $ttl, $callback) {
$value = $this->get($key);
if ($value !== null) {
return $value;
}
$value = $callback();
$this->put($key, $value, $ttl);
return $value;
}
public function rememberForever($key, $callback) {
return $this->remember($key, 0, $callback);
}
public function forget($key) {
$this->cache->delete($this->taggedItemKey($key));
$this->pullKey($key);
}
public function flush() {
foreach ($this->names as $name) {
$this->cache->flushTags($name);
}
}
private function taggedItemKey($key) {
return implode(':', array_merge($this->names, [$key]));
}
private function pushKey($key) {
foreach ($this->names as $name) {
$keys = $this->cache->get("tag:{$name}:keys") ?: [];
if (!in_array($key, $keys)) {
$keys[] = $key;
$this->cache->forever("tag:{$name}:keys", $keys);
}
}
}
private function pullKey($key) {
foreach ($this->names as $name) {
$keys = $this->cache->get("tag:{$name}:keys") ?: [];
if (($index = array_search($key, $keys)) !== false) {
unset($keys[$index]);
$this->cache->forever("tag:{$name}:keys", array_values($keys));
}
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace Donations\Core;
class Controller {
protected $config;
protected $db;
protected $logger;
protected $view;
protected $request;
protected $response;
public function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->db = db();
$this->logger = new Logger();
$this->view = new View();
$this->request = $this->parseRequest();
$this->response = [
'success' => false,
'message' => '',
'data' => null
];
}
protected function parseRequest() {
$request = [
'method' => $_SERVER['REQUEST_METHOD'],
'path' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),
'query' => $_GET,
'body' => json_decode(file_get_contents('php://input'), true) ?? [],
'headers' => getallheaders()
];
return $request;
}
protected function validateCsrf() {
if ($this->request['method'] === 'POST') {
$token = $_POST['csrf_token'] ?? null;
if (!$token || $token !== $_SESSION['csrf_token']) {
$this->error('Invalid CSRF token', 403);
return false;
}
}
return true;
}
protected function requireAuth() {
if (!isset($_SESSION['user_id'])) {
$this->redirect('/login');
return false;
}
return true;
}
protected function requireAdmin() {
if (!$this->requireAuth()) {
return false;
}
$sql = "SELECT is_admin FROM users WHERE id = ?";
$user = $this->db->fetch($sql, [$_SESSION['user_id']]);
if (!$user['is_admin']) {
$this->error('Unauthorized access', 403);
return false;
}
return true;
}
protected function redirect($path) {
header('Location: ' . $this->config['base_url'] . $path);
exit;
}
protected function json($data, $code = 200) {
http_response_code($code);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
protected function success($message, $data = null) {
$this->response = [
'success' => true,
'message' => $message,
'data' => $data
];
$this->json($this->response);
}
protected function error($message, $code = 400) {
$this->response = [
'success' => false,
'message' => $message,
'data' => null
];
$this->json($this->response, $code);
}
protected function validateInput($data, $rules) {
$errors = [];
foreach ($rules as $field => $rule) {
if (!isset($data[$field]) && strpos($rule, 'required') !== false) {
$errors[$field] = "The $field field is required.";
continue;
}
if (isset($data[$field])) {
if (strpos($rule, 'numeric') !== false && !is_numeric($data[$field])) {
$errors[$field] = "The $field must be a number.";
}
if (strpos($rule, 'min:') !== false) {
$min = substr($rule, strpos($rule, 'min:') + 4);
if ($data[$field] < $min) {
$errors[$field] = "The $field must be at least $min.";
}
}
if (strpos($rule, 'max:') !== false) {
$max = substr($rule, strpos($rule, 'max:') + 4);
if ($data[$field] > $max) {
$errors[$field] = "The $field must not be greater than $max.";
}
}
}
}
if (!empty($errors)) {
$this->error('Validation failed', 422);
}
return true;
}
protected function sanitizeInput($input) {
if (is_array($input)) {
return array_map([$this, 'sanitizeInput'], $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
protected function log($message, $level = 'info') {
$this->logger->log($message, $level);
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Donations\Core;
class Database {
private static $instance = null;
private $connection;
private $config;
private function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->connect();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function connect() {
try {
$dsn = "mysql:host={$this->config['db']['host']};dbname={$this->config['db']['name']};charset=utf8mb4";
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => false
];
$this->connection = new \PDO(
$dsn,
$this->config['db']['user'],
$this->config['db']['pass'],
$options
);
} catch (\PDOException $e) {
throw new DatabaseException("Connection failed: " . $e->getMessage());
}
}
public function query($sql, $params = []) {
try {
$stmt = $this->connection->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (\PDOException $e) {
throw new DatabaseException("Query failed: " . $e->getMessage());
}
}
public function fetch($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->fetch();
}
public function fetchAll($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->fetchAll();
}
public function execute($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->rowCount();
}
public function lastInsertId() {
return $this->connection->lastInsertId();
}
public function beginTransaction() {
return $this->connection->beginTransaction();
}
public function commit() {
return $this->connection->commit();
}
public function rollback() {
return $this->connection->rollBack();
}
public function quote($value) {
return $this->connection->quote($value);
}
public function getConnection() {
return $this->connection;
}
public function __destruct() {
$this->connection = null;
}
private function __clone() {}
private function __wakeup() {}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Donations\Core;
class Event {
private static $instance = null;
private $listeners = [];
private $queue;
private function __construct() {
$this->queue = Queue::getInstance();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function listen($event, $listener) {
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $listener;
}
public function fire($event, $data = []) {
if (!isset($this->listeners[$event])) {
return;
}
foreach ($this->listeners[$event] as $listener) {
if (is_string($listener)) {
$listener = new $listener();
}
if (method_exists($listener, 'handle')) {
$listener->handle($data);
}
}
}
public function dispatch($event, $data = []) {
$this->fire($event, $data);
}
public function dispatchAsync($event, $data = []) {
$this->queue->push('EventJob', [
'event' => $event,
'data' => $data
]);
}
public function dispatchDelayed($delay, $event, $data = []) {
$this->queue->later($delay, 'EventJob', [
'event' => $event,
'data' => $data
]);
}
public function forget($event) {
unset($this->listeners[$event]);
}
public function flush() {
$this->listeners = [];
}
public function hasListeners($event) {
return isset($this->listeners[$event]) && !empty($this->listeners[$event]);
}
public function getListeners($event) {
return $this->listeners[$event] ?? [];
}
public function getEvents() {
return array_keys($this->listeners);
}
public function subscribe($subscriber) {
if (method_exists($subscriber, 'subscribe')) {
$subscriber->subscribe($this);
}
}
public function unsubscribe($subscriber) {
foreach ($this->listeners as $event => $listeners) {
$this->listeners[$event] = array_filter($listeners, function($listener) use ($subscriber) {
return $listener !== $subscriber;
});
}
}
}
class EventJob {
private $event;
public function handle($data) {
$this->event = Event::getInstance();
$this->event->fire($data['event'], $data['data']);
}
}
class EventSubscriber {
public function subscribe($events) {
// Override this method to subscribe to events
}
}
class EventListener {
public function handle($data) {
// Override this method to handle events
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Donations\Core;
class Exception extends \Exception {
protected $data;
public function __construct($message = "", $code = 0, $data = null) {
parent::__construct($message, $code);
$this->data = $data;
}
public function getData() {
return $this->data;
}
public function toArray() {
return [
'success' => false,
'message' => $this->getMessage(),
'code' => $this->getCode(),
'data' => $this->getData()
];
}
public function toJson() {
return json_encode($this->toArray());
}
}
class ValidationException extends Exception {
public function __construct($errors) {
parent::__construct('Validation failed', 422, $errors);
}
}
class AuthenticationException extends Exception {
public function __construct($message = 'Authentication required') {
parent::__construct($message, 401);
}
}
class AuthorizationException extends Exception {
public function __construct($message = 'Unauthorized access') {
parent::__construct($message, 403);
}
}
class NotFoundException extends Exception {
public function __construct($message = 'Resource not found') {
parent::__construct($message, 404);
}
}
class RateLimitException extends Exception {
public function __construct($message = 'Rate limit exceeded') {
parent::__construct($message, 429);
}
}
class PaymentException extends Exception {
public function __construct($message, $data = null) {
parent::__construct($message, 402, $data);
}
}
class DatabaseException extends Exception {
public function __construct($message = 'Database error occurred') {
parent::__construct($message, 500);
}
}
class ConfigurationException extends Exception {
public function __construct($message = 'Configuration error occurred') {
parent::__construct($message, 500);
}
}
class WebhookException extends Exception {
public function __construct($message = 'Webhook error occurred') {
parent::__construct($message, 500);
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Donations\Core;
abstract class Handler {
protected $config;
protected $db;
protected $logger;
public function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->db = db();
$this->logger = new Logger();
}
protected function validate($data, $rules) {
$errors = [];
foreach ($rules as $field => $rule) {
if (!isset($data[$field]) && strpos($rule, 'required') !== false) {
$errors[$field] = "The $field field is required.";
continue;
}
if (isset($data[$field])) {
if (strpos($rule, 'numeric') !== false && !is_numeric($data[$field])) {
$errors[$field] = "The $field must be a number.";
}
if (strpos($rule, 'min:') !== false) {
$min = substr($rule, strpos($rule, 'min:') + 4);
if ($data[$field] < $min) {
$errors[$field] = "The $field must be at least $min.";
}
}
if (strpos($rule, 'max:') !== false) {
$max = substr($rule, strpos($rule, 'max:') + 4);
if ($data[$field] > $max) {
$errors[$field] = "The $field must not be greater than $max.";
}
}
if (strpos($rule, 'email') !== false && !filter_var($data[$field], FILTER_VALIDATE_EMAIL)) {
$errors[$field] = "The $field must be a valid email address.";
}
}
}
return $errors;
}
protected function log($message, $level = 'info') {
$this->logger->log($message, $level);
}
protected function error($message, $code = 500) {
$this->log($message, 'error');
throw new \Exception($message, $code);
}
protected function success($message, $data = null) {
$this->log($message, 'info');
return [
'success' => true,
'message' => $message,
'data' => $data
];
}
protected function formatAmount($amount) {
return number_format($amount, 2, '.', '');
}
protected function sanitizeInput($input) {
if (is_array($input)) {
return array_map([$this, 'sanitizeInput'], $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
protected function generateUniqueId($prefix = '') {
return $prefix . uniqid() . bin2hex(random_bytes(8));
}
protected function getClientIp() {
$ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
return $ip;
}
protected function isAllowedIp($ip) {
return empty($this->config['api']['allowed_ips']) ||
in_array($ip, $this->config['api']['allowed_ips']);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Donations\Core;
class Logger {
private $logFile;
private $logLevels = ['debug', 'info', 'warning', 'error', 'critical'];
public function __construct() {
$this->logFile = DONATIONS_PATH . '/logs/donations.log';
$this->ensureLogDirectory();
}
public function log($message, $level = 'info') {
if (!in_array($level, $this->logLevels)) {
$level = 'info';
}
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[$timestamp] [$level] $message" . PHP_EOL;
file_put_contents($this->logFile, $logMessage, FILE_APPEND);
}
private function ensureLogDirectory() {
$logDir = dirname($this->logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
}
public function debug($message) {
$this->log($message, 'debug');
}
public function info($message) {
$this->log($message, 'info');
}
public function warning($message) {
$this->log($message, 'warning');
}
public function error($message) {
$this->log($message, 'error');
}
public function critical($message) {
$this->log($message, 'critical');
}
public function getLogContents($lines = 100) {
if (!file_exists($this->logFile)) {
return [];
}
$logs = file($this->logFile);
return array_slice($logs, -$lines);
}
public function clearLog() {
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
}

View File

@@ -0,0 +1,190 @@
<?php
namespace Donations\Core;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
class Mail {
private static $instance = null;
private $config;
private $mailer;
private $queue;
private function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->queue = Queue::getInstance();
$this->setupMailer();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function setupMailer() {
$this->mailer = new PHPMailer(true);
try {
$this->mailer->isSMTP();
$this->mailer->Host = $this->config['mail']['host'];
$this->mailer->SMTPAuth = true;
$this->mailer->Username = $this->config['mail']['username'];
$this->mailer->Password = $this->config['mail']['password'];
$this->mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$this->mailer->Port = $this->config['mail']['port'];
$this->mailer->CharSet = 'UTF-8';
$this->mailer->setFrom(
$this->config['mail']['from']['address'],
$this->config['mail']['from']['name']
);
} catch (Exception $e) {
throw new \Exception("Mail setup failed: " . $e->getMessage());
}
}
public function to($address, $name = '') {
$this->mailer->addAddress($address, $name);
return $this;
}
public function cc($address, $name = '') {
$this->mailer->addCC($address, $name);
return $this;
}
public function bcc($address, $name = '') {
$this->mailer->addBCC($address, $name);
return $this;
}
public function subject($subject) {
$this->mailer->Subject = $subject;
return $this;
}
public function body($body) {
$this->mailer->Body = $body;
return $this;
}
public function html($html) {
$this->mailer->isHTML(true);
$this->mailer->Body = $html;
return $this;
}
public function text($text) {
$this->mailer->AltBody = $text;
return $this;
}
public function attach($path, $name = '') {
$this->mailer->addAttachment($path, $name);
return $this;
}
public function embed($path, $cid) {
$this->mailer->addEmbeddedImage($path, $cid);
return $this;
}
public function send() {
try {
return $this->mailer->send();
} catch (Exception $e) {
throw new \Exception("Mail send failed: " . $e->getMessage());
}
}
public function queue($delay = 0) {
$data = [
'to' => $this->mailer->getAllRecipientAddresses(),
'subject' => $this->mailer->Subject,
'body' => $this->mailer->Body,
'html' => $this->mailer->isHTML(),
'text' => $this->mailer->AltBody,
'attachments' => $this->getAttachments(),
'embedded' => $this->getEmbedded()
];
if ($delay > 0) {
return $this->queue->later($delay, 'MailJob', $data);
}
return $this->queue->push('MailJob', $data);
}
protected function getAttachments() {
$attachments = [];
foreach ($this->mailer->getAttachments() as $attachment) {
$attachments[] = [
'path' => $attachment[0],
'name' => $attachment[1]
];
}
return $attachments;
}
protected function getEmbedded() {
$embedded = [];
foreach ($this->mailer->getEmbeddedImages() as $image) {
$embedded[] = [
'path' => $image[0],
'cid' => $image[1]
];
}
return $embedded;
}
public function reset() {
$this->mailer->clearAllRecipients();
$this->mailer->clearAttachments();
$this->mailer->clearCustomHeaders();
$this->mailer->clearReplyTos();
$this->mailer->Subject = '';
$this->mailer->Body = '';
$this->mailer->AltBody = '';
$this->mailer->isHTML(false);
return $this;
}
public function getMailer() {
return $this->mailer;
}
}
class MailJob {
private $mail;
public function handle($data) {
$this->mail = Mail::getInstance();
foreach ($data['to'] as $address => $name) {
$this->mail->to($address, $name);
}
$this->mail->subject($data['subject'])
->body($data['body']);
if ($data['html']) {
$this->mail->html($data['body']);
}
if ($data['text']) {
$this->mail->text($data['text']);
}
foreach ($data['attachments'] as $attachment) {
$this->mail->attach($attachment['path'], $attachment['name']);
}
foreach ($data['embedded'] as $image) {
$this->mail->embed($image['path'], $image['cid']);
}
$this->mail->send();
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace Donations\Core;
abstract class Model {
protected $db;
protected $table;
protected $primaryKey = 'id';
public function __construct() {
$this->db = db();
}
public function find($id) {
$sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ?";
return $this->db->fetch($sql, [$id]);
}
public function all($conditions = [], $orderBy = null, $limit = null) {
$sql = "SELECT * FROM {$this->table}";
$params = [];
if (!empty($conditions)) {
$sql .= " WHERE " . $this->buildWhereClause($conditions, $params);
}
if ($orderBy) {
$sql .= " ORDER BY " . $orderBy;
}
if ($limit) {
$sql .= " LIMIT " . $limit;
}
return $this->db->fetchAll($sql, $params);
}
public function create($data) {
$fields = array_keys($data);
$values = array_values($data);
$placeholders = str_repeat('?,', count($fields) - 1) . '?';
$sql = "INSERT INTO {$this->table} (" . implode(',', $fields) . ")
VALUES ($placeholders)";
$this->db->execute($sql, $values);
return $this->db->lastInsertId();
}
public function update($id, $data) {
$fields = array_keys($data);
$values = array_values($data);
$set = implode('=?,', $fields) . '=?';
$values[] = $id;
$sql = "UPDATE {$this->table} SET $set
WHERE {$this->primaryKey} = ?";
return $this->db->execute($sql, $values);
}
public function delete($id) {
$sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?";
return $this->db->execute($sql, [$id]);
}
public function count($conditions = []) {
$sql = "SELECT COUNT(*) as count FROM {$this->table}";
$params = [];
if (!empty($conditions)) {
$sql .= " WHERE " . $this->buildWhereClause($conditions, $params);
}
$result = $this->db->fetch($sql, $params);
return $result['count'];
}
protected function buildWhereClause($conditions, &$params) {
$clauses = [];
foreach ($conditions as $field => $value) {
if (is_array($value)) {
$operator = $value[0];
$val = $value[1];
} else {
$operator = '=';
$val = $value;
}
$clauses[] = "$field $operator ?";
$params[] = $val;
}
return implode(' AND ', $clauses);
}
public function beginTransaction() {
return $this->db->beginTransaction();
}
public function commit() {
return $this->db->commit();
}
public function rollback() {
return $this->db->rollback();
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace Donations\Core;
class Queue {
private static $instance = null;
private $config;
private $connection;
private $channel;
private $queue;
private function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->connect();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function connect() {
try {
$this->connection = new \AMQPConnection([
'host' => $this->config['queue']['host'],
'port' => $this->config['queue']['port'],
'username' => $this->config['queue']['user'],
'password' => $this->config['queue']['pass'],
'vhost' => $this->config['queue']['vhost']
]);
$this->channel = $this->connection->channel();
$this->queue = $this->config['queue']['name'];
$this->channel->queue_declare($this->queue, false, true, false, false);
} catch (\Exception $e) {
throw new \Exception("Queue connection failed: " . $e->getMessage());
}
}
public function push($job, $data = [], $delay = 0) {
$message = [
'job' => $job,
'data' => $data,
'attempts' => 0,
'created_at' => time()
];
$msg = new \AMQPMessage(
json_encode($message),
['delivery_mode' => \AMQPMessage::DELIVERY_MODE_PERSISTENT]
);
if ($delay > 0) {
$this->channel->basic_publish($msg, '', $this->queue . '_delayed');
} else {
$this->channel->basic_publish($msg, '', $this->queue);
}
return true;
}
public function later($delay, $job, $data = []) {
return $this->push($job, $data, $delay);
}
public function pop() {
$msg = $this->channel->basic_get($this->queue);
if ($msg) {
$message = json_decode($msg->getBody(), true);
$message['attempts']++;
$this->channel->basic_ack($msg->getDeliveryTag());
return $message;
}
return null;
}
public function process() {
$this->channel->basic_qos(null, 1, null);
$callback = function($msg) {
try {
$message = json_decode($msg->getBody(), true);
$job = $message['job'];
$data = $message['data'];
$class = "Donations\\Jobs\\{$job}";
$instance = new $class();
$instance->handle($data);
$this->channel->basic_ack($msg->getDeliveryTag());
} catch (\Exception $e) {
$this->handleFailedJob($msg, $e);
}
};
$this->channel->basic_consume($this->queue, '', false, false, false, false, $callback);
while ($this->channel->is_consuming()) {
$this->channel->wait();
}
}
protected function handleFailedJob($msg, $exception) {
$message = json_decode($msg->getBody(), true);
$message['attempts']++;
if ($message['attempts'] >= $this->config['queue']['max_attempts']) {
$this->channel->basic_nack($msg->getDeliveryTag(), false, false);
$this->logFailedJob($message, $exception);
} else {
$this->channel->basic_nack($msg->getDeliveryTag(), false, true);
}
}
protected function logFailedJob($message, $exception) {
$sql = "INSERT INTO failed_jobs (job, data, error, failed_at) VALUES (?, ?, ?, NOW())";
$this->db->execute($sql, [
$message['job'],
json_encode($message['data']),
$exception->getMessage()
]);
}
public function size() {
$queueInfo = $this->channel->queue_declare($this->queue, false, true, false, false);
return $queueInfo[1];
}
public function flush() {
$this->channel->queue_purge($this->queue);
}
public function retry($id) {
$sql = "SELECT * FROM failed_jobs WHERE id = ?";
$job = $this->db->fetch($sql, [$id]);
if ($job) {
$this->push($job['job'], json_decode($job['data'], true));
$this->db->execute("DELETE FROM failed_jobs WHERE id = ?", [$id]);
return true;
}
return false;
}
public function forget($id) {
return $this->db->execute("DELETE FROM failed_jobs WHERE id = ?", [$id]);
}
public function getConnection() {
return $this->connection;
}
public function __destruct() {
if ($this->channel) {
$this->channel->close();
}
if ($this->connection) {
$this->connection->close();
}
}
private function __clone() {}
private function __wakeup() {}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Donations\Core;
class Router {
protected $routes = [];
protected $config;
public function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
}
public function add($method, $path, $handler) {
$this->routes[] = [
'method' => strtoupper($method),
'path' => $path,
'handler' => $handler
];
}
public function get($path, $handler) {
$this->add('GET', $path, $handler);
}
public function post($path, $handler) {
$this->add('POST', $path, $handler);
}
public function put($path, $handler) {
$this->add('PUT', $path, $handler);
}
public function delete($path, $handler) {
$this->add('DELETE', $path, $handler);
}
public function dispatch() {
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = str_replace($this->config['base_url'], '', $path);
foreach ($this->routes as $route) {
if ($route['method'] !== $method) {
continue;
}
$pattern = $this->convertPathToRegex($route['path']);
if (preg_match($pattern, $path, $matches)) {
array_shift($matches); // Remove the full match
return $this->executeHandler($route['handler'], $matches);
}
}
// No route found
http_response_code(404);
echo json_encode([
'success' => false,
'message' => 'Route not found',
'data' => null
]);
}
protected function convertPathToRegex($path) {
return '#^' . preg_replace('#\{([a-zA-Z0-9_]+)\}#', '([^/]+)', $path) . '$#';
}
protected function executeHandler($handler, $params) {
if (is_callable($handler)) {
return call_user_func_array($handler, $params);
}
if (is_string($handler)) {
list($controller, $method) = explode('@', $handler);
$controllerClass = "Donations\\Controllers\\{$controller}";
$controllerInstance = new $controllerClass();
return call_user_func_array([$controllerInstance, $method], $params);
}
throw new \Exception('Invalid handler');
}
public function group($prefix, $routes) {
foreach ($routes as $route) {
$this->add(
$route['method'],
$prefix . $route['path'],
$route['handler']
);
}
}
public function resource($name, $controller) {
$this->get("/{$name}", "{$controller}@index");
$this->get("/{$name}/create", "{$controller}@create");
$this->post("/{$name}", "{$controller}@store");
$this->get("/{$name}/{id}", "{$controller}@show");
$this->get("/{$name}/{id}/edit", "{$controller}@edit");
$this->put("/{$name}/{id}", "{$controller}@update");
$this->delete("/{$name}/{id}", "{$controller}@destroy");
}
public function apiResource($name, $controller) {
$this->get("/api/{$name}", "{$controller}@index");
$this->post("/api/{$name}", "{$controller}@store");
$this->get("/api/{$name}/{id}", "{$controller}@show");
$this->put("/api/{$name}/{id}", "{$controller}@update");
$this->delete("/api/{$name}/{id}", "{$controller}@destroy");
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Donations\Core;
abstract class Service {
protected $config;
protected $db;
protected $logger;
public function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->db = db();
$this->logger = new Logger();
}
protected function beginTransaction() {
return $this->db->beginTransaction();
}
protected function commit() {
return $this->db->commit();
}
protected function rollback() {
return $this->db->rollback();
}
protected function validate($data, $rules) {
$errors = [];
foreach ($rules as $field => $rule) {
if (!isset($data[$field]) && strpos($rule, 'required') !== false) {
$errors[$field] = "The $field field is required.";
continue;
}
if (isset($data[$field])) {
if (strpos($rule, 'numeric') !== false && !is_numeric($data[$field])) {
$errors[$field] = "The $field must be a number.";
}
if (strpos($rule, 'min:') !== false) {
$min = substr($rule, strpos($rule, 'min:') + 4);
if ($data[$field] < $min) {
$errors[$field] = "The $field must be at least $min.";
}
}
if (strpos($rule, 'max:') !== false) {
$max = substr($rule, strpos($rule, 'max:') + 4);
if ($data[$field] > $max) {
$errors[$field] = "The $field must not be greater than $max.";
}
}
}
}
if (!empty($errors)) {
throw new \Exception(json_encode($errors));
}
return true;
}
protected function sanitizeInput($input) {
if (is_array($input)) {
return array_map([$this, 'sanitizeInput'], $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
protected function log($message, $level = 'info') {
$this->logger->log($message, $level);
}
protected function formatAmount($amount) {
return number_format($amount, 2, '.', '');
}
protected function generateUniqueId($prefix = '') {
return $prefix . uniqid() . bin2hex(random_bytes(8));
}
protected function getClientIp() {
$ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
return $ip;
}
protected function isAllowedIp($ip) {
return empty($this->config['api']['allowed_ips']) ||
in_array($ip, $this->config['api']['allowed_ips']);
}
protected function formatDate($date, $format = 'Y-m-d H:i:s') {
return date($format, strtotime($date));
}
protected function truncate($text, $length = 100) {
if (strlen($text) <= $length) {
return $text;
}
return substr($text, 0, $length) . '...';
}
protected function escape($text) {
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
protected function isActive($path) {
return strpos($_SERVER['REQUEST_URI'], $path) !== false ? 'active' : '';
}
}

View File

@@ -0,0 +1,209 @@
<?php
namespace Donations\Core;
class Session {
private static $instance = null;
private $config;
private $started = false;
private function __construct() {
$this->config = require DONATIONS_PATH . '/config/config.php';
$this->start();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function start() {
if (!$this->started) {
if (session_status() === PHP_SESSION_NONE) {
session_start([
'cookie_httponly' => true,
'cookie_secure' => $this->config['session']['secure'],
'cookie_samesite' => 'Lax',
'gc_maxlifetime' => $this->config['session']['lifetime'] * 60
]);
}
$this->started = true;
}
}
public function get($key, $default = null) {
return $_SESSION[$key] ?? $default;
}
public function set($key, $value) {
$_SESSION[$key] = $value;
}
public function has($key) {
return isset($_SESSION[$key]);
}
public function remove($key) {
if (isset($_SESSION[$key])) {
unset($_SESSION[$key]);
}
}
public function all() {
return $_SESSION;
}
public function flush() {
$_SESSION = [];
}
public function regenerate($destroy = false) {
session_regenerate_id($destroy);
}
public function destroy() {
session_destroy();
$this->started = false;
}
public function flash($key, $value = null) {
if ($value !== null) {
$_SESSION['_flash'][$key] = $value;
} else {
$value = $_SESSION['_flash'][$key] ?? null;
unset($_SESSION['_flash'][$key]);
return $value;
}
}
public function reflash() {
if (isset($_SESSION['_flash'])) {
$_SESSION['_flash'] = array_merge($_SESSION['_flash'], $_SESSION['_flash']);
}
}
public function keep($keys = null) {
if ($keys === null) {
$_SESSION['_flash'] = [];
} else {
foreach ((array) $keys as $key) {
if (isset($_SESSION['_flash'][$key])) {
$_SESSION['_flash'][$key] = $_SESSION['_flash'][$key];
}
}
}
}
public function token() {
if (!isset($_SESSION['_token'])) {
$_SESSION['_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['_token'];
}
public function regenerateToken() {
$_SESSION['_token'] = bin2hex(random_bytes(32));
}
public function previousUrl() {
return $_SESSION['_previous_url'] ?? null;
}
public function intended($default = null) {
return $_SESSION['_intended_url'] ?? $default;
}
public function setIntendedUrl($url) {
$_SESSION['_intended_url'] = $url;
}
public function pull($key, $default = null) {
$value = $this->get($key, $default);
$this->remove($key);
return $value;
}
public function increment($key, $value = 1) {
if (!isset($_SESSION[$key])) {
$_SESSION[$key] = 0;
}
$_SESSION[$key] += $value;
return $_SESSION[$key];
}
public function decrement($key, $value = 1) {
return $this->increment($key, -$value);
}
public function put($key, $value) {
return $this->set($key, $value);
}
public function forget($key) {
return $this->remove($key);
}
public function exists($key) {
return $this->has($key);
}
public function missing($key) {
return !$this->has($key);
}
public function save() {
if ($this->started) {
session_write_close();
$this->started = false;
}
}
public function getId() {
return session_id();
}
public function setId($id) {
if ($this->started) {
session_write_close();
}
session_id($id);
$this->start();
}
public function getName() {
return session_name();
}
public function setName($name) {
if ($this->started) {
session_write_close();
}
session_name($name);
$this->start();
}
public function getHandler() {
return session_get_handler();
}
public function setHandler($handler) {
if ($this->started) {
session_write_close();
}
session_set_save_handler($handler);
$this->start();
}
public function getCookieParams() {
return session_get_cookie_params();
}
public function setCookieParams($lifetime, $path = '/', $domain = '', $secure = false, $httponly = true) {
if ($this->started) {
session_write_close();
}
session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly);
$this->start();
}
}

Some files were not shown because too many files have changed in this diff Show More