AdgoalController.php 6.1 KB
Newer Older
Dominik Hebeler's avatar
Dominik Hebeler committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use LaravelLocalization;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

/**
 * Before we redirect users to the affiliate shops we will track the clicks ourself.
 * Reason is that many Affiliate shops redirect to invalid URLs (404, 500, ... Errors) which leads to bad user experience.
 * We will store the clicked Affiliate Link together with the final URL the user should land on.
 * No userdata or other metadata will be stored together with that information.
 * 
 * That way we can do manual validation of affiliate links and exclude bad partnershop links to be shown in the future. Since this will be
 * a lot of links we count the clicks so we can validate the most used ones first.
 */

class AdgoalController extends Controller
{
    # Data will be stored for 24 hours
    const STORAGE_DURATION_HOURS = 24;
    const REDIS_STORAGE_KEY = "affiliate_click";

    /**
     * This function is called when a user clicks on a affiliate link. It will first validate that the URL
     * was generated by us to prevent random redirect links to be manually created for our domains.
     * After that we will store the necessary information (link and affiliate link) into our database.
     * After that we will redirect the user to the affiliate shop
     */
    public function forward(Request $request){
        // $link = "https://metager.de";
        // $affillink = "https://test.de";
        // $password = self::generatePassword($affillink, $link);
        // dd(route('adgoal-redirect', ["link" => $link, "affillink" => $affillink, "password" => $password]));
        /**
         * Get Parameters (Result informations)
         * 1. affillink (With Affiliate Redirect)
         * 2. link
         * 5. Password (hmac with adgoal private key and the two parameters)
         */
        $request->validate([
            'affillink' => ['required', 'url', 'active_url'],
            'link' => ['required', 'url', 'active_url'],
            # Validation of redirect request so that one cannot generate random redirect URLs pointing to our domains
            'password' => function($attribute, $value, $fail) use($request) {
                // Check if hmac matches
                $correctPassword = self::generatePassword($request->input('affillink'), $request->input('link'));
                if(!hash_equals($correctPassword, $value)){
                    $fail('The given password is incorrect!');
                }
            }
        ]);

        $this->storePartnerCallFast($request->input('affillink'), $request->input('link'));

        return redirect($request->input('affillink'));
    }

    /**
     * Stores Click information into Redis Cache for fast execution since this is synchronous call
     * at search time.
     * A Cronjob will pick the data up and store it into Mariadb later (see self::storePartnerCall)
     */
    private function storePartnerCallFast($affillink, $link) {
        # Generate Data to store
        $host = parse_url($link, PHP_URL_HOST);
        if(empty($host)){
            return;
        }
        $storeObject = [
            "host" => $host,
            "affillink" => $affillink,
            "link" => $link,
        ];


        # Store Data in Redis
        $redis = Redis::connection(config('cache.stores.redis.connection'));
        $redis->rpush($this::REDIS_STORAGE_KEY, json_encode($storeObject));
    }

    public static function storePartnerCalls() {
        $redis = Redis::connection(config('cache.stores.redis.connection'));
        DB::transaction(function() use($redis){
            while(!empty($data = $redis->lpop(self::REDIS_STORAGE_KEY))){
                $data = json_decode($data, true);
                # Insert data into mariadb table
                DB::insert('insert into affiliate_clicks (hostname, affillink, link) values (?, ?, ?)', 
                    [$data["host"], $data["affillink"], $data["link"]]);
            }
        });
    }

    /**
     * Generates a Redirect URL for our partnershops
     */
    public static function generateRedirectUrl($affillink, $link){
        $password = self::generatePassword($affillink, $link);
        return LaravelLocalization::getLocalizedURL(
            LaravelLocalization::getCurrentLocale(), 
            route('adgoal-redirect', ["link" => $link, "affillink" => $affillink, "password" => $password])
        );
    }

    /**
     * Generates hmac password to validate redirect URLs
     */
    public static function generatePassword($affillink, $link){
        return hash_hmac("sha256", $affillink . $link, config('metager.metager.adgoal.private_key'));
    }
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161


    /**
     * Routes for the Admin Interface
     */
    public function adminIndex(Request $request){
        return view('admin.affiliates.index')
            ->with('title', "Affilliates Overview - MetaGer")
            ->with('css', [
                mix('/css/admin/affilliates/index.css')
            ])
            ->with('darkcss', [
                mix('/css/admin/affilliates/index-dark.css')
            ])
            ->with('js', [
                mix('/js/admin/affilliates.js')
            ]);
    }

    public function blacklistJson(Request $request){
        $request->validate([
            "blacklist" => 'boolean'
        ]);

        $count = 5; # How Many results to return
        $skip = 0; # How many results to skip
        $blacklist = $request->input('blacklist', true);

        $total = DB::select("select count(*) as total_rows from affiliate_blacklist");
        $total = intval($total[0]->{"total_rows"});
        $blacklistItems = DB::select('select * from affiliate_blacklist where blacklist = ? order by created_at desc limit ? offset ?', [$blacklist, $count, $skip]);
        
        $result = [
            "count" => $count,
            "skip" => $skip,
            "total" => $total,
            "results" => $blacklistItems
        ];

        return response()->json($result);
    }

    public function whitelistJson(Request $request){
        $input = $request->all();
        $input["blacklist"] = true;
        $request->replace($input);
        return $this->blacklistJson($request);
    }

Dominik Hebeler's avatar
Dominik Hebeler committed
162
}