Transaction::getDonations()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 0
cts 25
cp 0
rs 9.52
c 0
b 0
f 0
cc 4
nc 2
nop 0
crap 20
1
<?php
2
$defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1;
3
4
class Transaction extends Base {
5
  protected $table = 'transactions';
6
  public $num_rows = 0, $insert_id = 0;
7
8
  /**
9
   * Add a new transaction to our class table
10
   * We also store the inserted ID in case the user needs it
11
   * @param account_id int Account ID to book transaction for
12
   * @param amount float Coin amount
13
   * @param type string Transaction type [Credit, Debit_AP, Debit_MP, Fee, Donation, Orphan_Credit, Orphan_Fee, Orphan_Donation]
14
   * @param block_id int Block ID to link transaction to [optional]
15
   * @param coin_address string Coin address for this transaction [optional]
16
   * @return bool
17
   **/
18
  public function addTransaction($account_id, $amount, $type='Credit', $block_id=NULL, $coin_address=NULL, $txid=NULL) {
19
    $amount = number_format($amount, $this->coin->getCoinValuePrevision(), '.', '');
0 ignored issues
show
Bug introduced by
The property coin does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
20
    $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, amount, block_id, type, coin_address, txid) VALUES (?, ?, ?, ?, ?, ?)");
21
    if ($this->checkStmt($stmt) && $stmt->bind_param("isisss", $account_id, $amount, $block_id, $type, $coin_address, $txid) && $stmt->execute()) {
22
      $this->insert_id = $stmt->insert_id;
23
      return true;
24
    }
25
    return $this->sqlError();
26
  }
27
28
  /**
29
   * Update a transaction with a RPC transaction ID
30
   * @param id integer Transaction ID
31
   * @param txid string RPC Transaction Identifier
32
   * @return bool true or false
33
   **/
34
  public function setRPCTxId($transaction_id, $rpc_txid=NULL) {
35
    $stmt = $this->mysqli->prepare("UPDATE $this->table SET txid = ? WHERE id = ?");
36
    if ($this->checkStmt($stmt) && $stmt->bind_param('si', $rpc_txid, $transaction_id) && $stmt->execute())
37
      return true;
38
    return $this->sqlError();
39
  }
40
41
  /*
42
   * Mark transactions of a user as archived
43
   * @param account_id int Account ID
44
   * @param txid int Transaction ID to start from
45
   * @param bool boolean True or False
46
   **/
47 View Code Duplication
  public function setArchived($account_id, $txid) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
48
    // Update all paid out transactions as archived
49
    $stmt = $this->mysqli->prepare("
50
      UPDATE $this->table AS t
51
      LEFT JOIN " . $this->block->getTableName() . " AS b
0 ignored issues
show
Bug introduced by
The property block does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
52
      ON b.id = t.block_id
53
      SET t.archived = 1
54
      WHERE t.archived = 0
55
      AND (
56
           ( t.account_id = ? AND t.id <= ? AND b.confirmations >= ? )
57
        OR ( t.account_id = ? AND t.id <= ? AND b.confirmations = -1 )
58
        OR ( t.account_id = ? AND t.id <= ? AND t.type IN ( 'Credit_PPS', 'Donation_PPS', 'Fee_PPS', 'TXFee', 'Debit_MP', 'Debit_AP' ) )
59
      )");
60
     if ($this->checkStmt($stmt) && $stmt->bind_param('iiiiiii', $account_id, $txid, $this->config['confirmations'], $account_id, $txid, $account_id, $txid) && $stmt->execute())
0 ignored issues
show
Bug introduced by
The property config does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
61
      return true;
62
    return $this->sqlError();
63
  }
64
65
  /**
66
   * Fetch a transaction summary by type with total amounts
67
   * @param account_id int Account ID, NULL for all
68
   * @return data array type and total
69
   **/
70
  public function getTransactionSummary($account_id=NULL) {
71
    if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data;
0 ignored issues
show
Bug introduced by
The property memcache does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
72
    $sql = "
73
      SELECT
74
        SUM(t.amount) AS total, t.type AS type
75
      FROM $this->table AS t
76
      LEFT OUTER JOIN " . $this->block->getTableName() . " AS b
77
      ON b.id = t.block_id
78
      WHERE ( b.confirmations > 0 OR b.id IS NULL )";
79
    if (!empty($account_id)) {
80
      $sql .= " AND t.account_id = ? ";
81
      $this->addParam('i', $account_id);
82
    }
83
    $sql .= " GROUP BY t.type";
84
    $stmt = $this->mysqli->prepare($sql);
85
    if (!empty($account_id)) {
86 View Code Duplication
      if (!($this->checkStmt($stmt) && call_user_func_array( array($stmt, 'bind_param'), $this->getParam()) && $stmt->execute()))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Transaction::getTransactionSummary of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
88
      $result = $stmt->get_result();
89
    } else {
90
      if (!($this->checkStmt($stmt) && $stmt->execute()))
91
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Transaction::getTransactionSummary of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
92
      $result = $stmt->get_result();
93
    }
94
    if ($result) {
95
      $aData = NULL;
96
      while ($row = $result->fetch_assoc()) {
97
        $aData[$row['type']] = $row['total'];
98
      }
99
      // Cache data for a while, query takes long on many rows
100
      return $this->memcache->setCache(__FUNCTION__ . $account_id, $aData, 60);
101
    }
102
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Transaction::getTransactionSummary of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
103
  }
104
105
106
  /**
107
   * Fetch a transaction summary by user with total amounts
108
   * @param account_id int Account ID, NULL for all
109
   * @return data array type and total
110
   **/
111 View Code Duplication
  public function getTransactionTypebyTime($account_id=NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
    $this->debug->append("STA " . __METHOD__, 4);
113
    if ($data = $this->memcache->get(__FUNCTION__)) return $data;
114
    $stmt = $this->mysqli->prepare("
115
      SELECT
116
        IFNULL(SUM(IF(t.type = 'Credit' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourCredit,
117
        IFNULL(SUM(IF(t.type = 'Bonus' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourBonus,
118
        IFNULL(SUM(IF(t.type = 'Debit_MP' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourDebitMP,
119
        IFNULL(SUM(IF(t.type = 'Debit_AP' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourDebitAP,
120
        IFNULL(SUM(IF(t.type = 'TXFee' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourTXFee,
121
        IFNULL(SUM(IF(t.type = 'Fee' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourFee,
122
        IFNULL(SUM(IF(t.type = 'Donation' AND timestamp >= DATE_SUB(now(), INTERVAL 3600 SECOND), t.amount, 0)), 0) AS 1HourDonation,
123
124
        IFNULL(SUM(IF(t.type = 'Credit' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourCredit,
125
        IFNULL(SUM(IF(t.type = 'Bonus' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourBonus,
126
        IFNULL(SUM(IF(t.type = 'Debit_MP' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourDebitMP,
127
        IFNULL(SUM(IF(t.type = 'Debit_AP' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourDebitAP,
128
        IFNULL(SUM(IF(t.type = 'TXFee' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourTXFee,
129
        IFNULL(SUM(IF(t.type = 'Fee' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourFee,
130
        IFNULL(SUM(IF(t.type = 'Donation' AND timestamp >= DATE_SUB(now(), INTERVAL 86400 SECOND), t.amount, 0)), 0) AS 24HourDonation,
131
132
        IFNULL(SUM(IF(t.type = 'Credit' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekCredit,
133
        IFNULL(SUM(IF(t.type = 'Bonus' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekBonus,
134
        IFNULL(SUM(IF(t.type = 'Debit_MP' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekDebitMP,
135
        IFNULL(SUM(IF(t.type = 'Debit_AP' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekDebitAP,
136
        IFNULL(SUM(IF(t.type = 'TXFee' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekTXFee,
137
        IFNULL(SUM(IF(t.type = 'Fee' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekFee,
138
        IFNULL(SUM(IF(t.type = 'Donation' AND timestamp >= DATE_SUB(now(), INTERVAL 604800 SECOND), t.amount, 0)), 0) AS 1WeekDonation,
139
140
        IFNULL(SUM(IF(t.type = 'Credit' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthCredit,
141
        IFNULL(SUM(IF(t.type = 'Bonus' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthBonus,
142
        IFNULL(SUM(IF(t.type = 'Debit_MP' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthDebitMP,
143
        IFNULL(SUM(IF(t.type = 'Debit_AP' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthDebitAP,
144
        IFNULL(SUM(IF(t.type = 'TXFee' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthTXFee,
145
        IFNULL(SUM(IF(t.type = 'Fee' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthFee,
146
        IFNULL(SUM(IF(t.type = 'Donation' AND timestamp >= DATE_SUB(now(), INTERVAL 2419200 SECOND), t.amount, 0)), 0) AS 1MonthDonation,
147
148
        IFNULL(SUM(IF(t.type = 'Credit' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearCredit,
149
        IFNULL(SUM(IF(t.type = 'Bonus' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearBonus,
150
        IFNULL(SUM(IF(t.type = 'Debit_MP' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearDebitMP,
151
        IFNULL(SUM(IF(t.type = 'Debit_AP' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearDebitAP,
152
        IFNULL(SUM(IF(t.type = 'TXFee' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearTXFee,
153
        IFNULL(SUM(IF(t.type = 'Fee' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearFee,
154
        IFNULL(SUM(IF(t.type = 'Donation' AND timestamp >= DATE_SUB(now(), INTERVAL 31536000 SECOND), t.amount, 0)), 0) AS 1YearDonation
155
      FROM $this->table AS t
156
      LEFT OUTER JOIN " . $this->block->getTableName() . " AS b ON b.id = t.block_id
157
      WHERE
158
        t.account_id = ? AND (b.confirmations > 0 OR b.id IS NULL)");
159
    if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result())
160
    	return $this->memcache->setCache(__FUNCTION__ . $account_id, $result->fetch_assoc(), 60);
161
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Transaction::getTransactionTypebyTime of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
162
  }
163
164
  /**
165
   * Get all transactions from start for account_id
166
   * @param start int Starting point, id of transaction
167
   * @param filter array Filter to limit transactions
168
   * @param limit int Only display this many transactions
169
   * @param account_id int Account ID
170
   * @return data array Database fields as defined in SELECT
171
   **/
172
  public function getTransactions($start=0, $filter=NULL, $limit=30, $account_id=NULL) {
173
    $this->debug->append("STA " . __METHOD__, 4);
174
    $sql = "
175
      SELECT
176
        t.id AS id,
177
        a.username as username,
178
        t.type AS type,
179
        t.amount AS amount,
180
        t.coin_address AS coin_address,
181
        t.timestamp AS timestamp,
182
        t.txid AS txid,
183
        b.height AS height,
184
        b.blockhash AS blockhash,
185
        b.confirmations AS confirmations
186
      FROM $this->table AS t
187
      LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id
188
      LEFT JOIN " . $this->user->getTableName() . " AS a ON t.account_id = a.id";
0 ignored issues
show
Bug introduced by
The property user does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
189
    if (!empty($account_id)) {
190
      $sql .= " WHERE ( t.account_id = ? ) ";
191
      $this->addParam('i', $account_id);
192
    }
193
    if (is_array($filter)) {
194
      $aFilter = array();
195
      foreach ($filter as $key => $value) {
196
        if (!empty($value)) {
197
          switch ($key) {
198
          case 'type':
199
            $aFilter[] = "( t.type = ? )";
200
            $this->addParam('s', $value);
201
            break;
202
          case 'status':
203
            switch ($value) {
204
            case 'Confirmed':
205
              if (empty($filter['type']) || ($filter['type'] != 'Debit_AP' && $filter['type'] != 'Debit_MP' && $filter['type'] != 'TXFee' && $filter['type'] != 'Credit_PPS' && $filter['type'] != 'Fee_PPS' && $filter['type'] != 'Donation_PPS')) {
206
                $aFilter[] = "( b.confirmations >= " . $this->config['confirmations'] . " OR ISNULL(b.confirmations) )";
207
              }
208
                break;
209
            case 'Unconfirmed':
210
              $aFilter[] = "( b.confirmations < " . $this->config['confirmations'] . " AND b.confirmations >= 0 )";
211
                break;
212
            case 'Orphan':
213
              $aFilter[] = "( b.confirmations = -1 )";
214
                break;
215
            }
216
            break;
217
            case 'account':
218
              $aFilter[] = "( LOWER(a.username) = LOWER(?) )";
219
              $this->addParam('s', $value);
220
              break;
221
            case 'address':
222
              $aFilter[] = "( t.coin_address = ? )";
223
              $this->addParam('s', $value);
224
              break;
225
          }
226
        }
227
      }
228
      if (!empty($aFilter)) {
229
      	empty($account_id) ? $sql .= " WHERE " : $sql .= " AND ";
230
        $sql .= implode(' AND ', $aFilter);
231
      }
232
    }
233
    $sql .= " ORDER BY id DESC LIMIT ?,?";
234
    // Add some other params to query
235
    $this->addParam('i', $start);
236
    $this->addParam('i', $limit);
237
    $stmt = $this->mysqli->prepare($sql);
238
    if ($this->checkStmt($stmt) && call_user_func_array( array($stmt, 'bind_param'), $this->getParam()) && $stmt->execute() && $result = $stmt->get_result())
239
      return $result->fetch_all(MYSQLI_ASSOC);
240
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Transaction::getTransactions of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
241
  }
242
243
  /**
244
   * Get all different transaction types
245
   * @return mixed array/bool Return types on succes, false on failure
246
   **/
247
  public function getTypes() {
248
    $stmt = $this->mysqli->prepare("SELECT DISTINCT type FROM $this->table");
249 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
      $aData = array('' => '');
251
      while ($row = $result->fetch_assoc()) {
252
        $aData[$row['type']] = $row['type'];
253
      }
254
      return $aData;
255
    }
256
    return $this->sqlError();
257
  }
258
259
  /**
260
   * Get all donation transactions
261
   * Used on donors page
262
   * return data array Donors and amounts
263
   **/
264
  public function getDonations() {
265
    $this->debug->append("STA " . __METHOD__, 4);
266
    $stmt = $this->mysqli->prepare("
267
      SELECT
268
        SUM(t.amount) AS donation,
269
        a.username AS username,
270
        a.is_anonymous AS is_anonymous,
271
        ROUND(a.donate_percent, 2) AS donate_percent
272
      FROM $this->table AS t
273
      LEFT JOIN " . $this->user->getTableName() . " AS a
274
      ON t.account_id = a.id
275
      LEFT JOIN " . $this->block->getTableName() . " AS b
276
      ON t.block_id = b.id
277
      WHERE
278
      (
279
        ( t.type = 'Donation' AND b.confirmations >= " . $this->config['confirmations'] . " ) OR
280
        t.type = 'Donation_PPS'
281
      )
282
      GROUP BY a.username
283
      ORDER BY donation DESC
284
      ");
285
    if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result())
286
      return $result->fetch_all(MYSQLI_ASSOC);
287
    return $this->sqlError();
288
  }
289
290
  /**
291
   * Get total balance for all users locked in wallet
292
   * This includes any outstanding unconfirmed transactions!
293
   * @param none
294
   * @return data double Amount locked for users
295
   **/
296
  public function getLockedBalance() {
297
    $this->debug->append("STA " . __METHOD__, 4);
298
    $stmt = $this->mysqli->prepare("
299
      SELECT
300
        (
301
          SUM( IF( ( t.type IN ('Credit','Bonus') AND b.confirmations >= ? ) OR t.type = 'Credit_PPS', t.amount, 0 ) ) -
302
          SUM( IF( t.type IN ('Debit_MP', 'Debit_AP'), t.amount, 0 ) ) -
303
          SUM( IF( ( t.type IN ('Donation','Fee') AND b.confirmations >= ? ) OR ( t.type IN ('Donation_PPS', 'Fee_PPS', 'TXFee') ), t.amount, 0 ) )
304
        ) AS balance
305
      FROM $this->table AS t
306
      LEFT JOIN " . $this->block->getTableName() . " AS b
307
      ON t.block_id = b.id
308
      WHERE archived = 0");
309 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $this->config['confirmations'], $this->config['confirmations']) && $stmt->execute() && $stmt->bind_result($dBalance) && $stmt->fetch())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
      return $dBalance;
0 ignored issues
show
Bug introduced by
The variable $dBalance does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
311
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Transaction::getLockedBalance of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
312
  }
313
314
  /**
315
   * Get an accounts total balance, ignore archived entries
316
   * @param account_id int Account ID
317
   * @return data float Credit - Debit - Fees - Donation
318
   **/
319
  public function getBalance($account_id) {
320
    $this->debug->append("STA " . __METHOD__, 4);
321
    $stmt = $this->mysqli->prepare("
322
      SELECT
323
        IFNULL((
324
          SUM( IF( ( t.type IN ('Credit','Bonus') AND b.confirmations >= ? ) OR t.type = 'Credit_PPS', t.amount, 0 ) ) -
325
          SUM( IF( t.type IN ('Debit_MP', 'Debit_AP'), t.amount, 0 ) ) -
326
          SUM( IF( ( t.type IN ('Donation','Fee') AND b.confirmations >= ? ) OR ( t.type IN ('Donation_PPS', 'Fee_PPS', 'TXFee') ), t.amount, 0 ) )
327
        ), 0) AS confirmed,
328
        IFNULL((
329
          SUM( IF( t.type IN ('Credit','Bonus') AND b.confirmations < ? AND b.confirmations >= 0, t.amount, 0 ) ) -
330
          SUM( IF( t.type IN ('Donation','Fee') AND b.confirmations < ? AND b.confirmations >= 0, t.amount, 0 ) )
331
        ), 0) AS unconfirmed,
332
        IFNULL((
333
          SUM( IF( t.type IN ('Credit','Bonus') AND b.confirmations = -1, t.amount, 0) ) -
334
          SUM( IF( t.type IN ('Donation','Fee') AND b.confirmations = -1, t.amount, 0) )
335
        ), 0) AS orphaned
336
      FROM $this->table AS t
337
      LEFT JOIN " . $this->block->getTableName() . " AS b
338
      ON t.block_id = b.id
339
      WHERE t.account_id = ?
340
      AND archived = 0
341
      ");
342
    if ($this->checkStmt($stmt) && $stmt->bind_param("iiiii", $this->config['confirmations'], $this->config['confirmations'], $this->config['confirmations'], $this->config['confirmations'], $account_id) && $stmt->execute() && $result = $stmt->get_result())
343
      return $result->fetch_assoc();
344
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Transaction::getBalance of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
345
  }
346
347
  /**
348
   * Get our Auto Payout queue
349
   * @param none
350
   * @return data array Account settings and confirmed balances
351
   **/
352
  public function getAPQueue($limit=250) {
353
    $this->debug->append("STA " . __METHOD__, 4);
354
    $stmt = $this->mysqli->prepare("
355
      SELECT
356
        a.id,
357
        a.username,
358
        ca.ap_threshold,
359
        ca.coin_address,
360
        IFNULL(
361
            (
362
              SUM( IF( ( t.type IN ('Credit','Bonus') AND b.confirmations >= " . $this->config['confirmations'] . ") OR t.type = 'Credit_PPS', t.amount, 0 ) ) -
363
              SUM( IF( t.type IN ('Debit_MP', 'Debit_AP'), t.amount, 0 ) ) -
364
              SUM( IF( ( t.type IN ('Donation','Fee') AND b.confirmations >= " . $this->config['confirmations'] . ") OR ( t.type IN ('Donation_PPS', 'Fee_PPS', 'TXFee') ), t.amount, 0 ) )
365
          ), 0
366
        ) AS confirmed
367
      FROM $this->table AS t
368
      LEFT JOIN " . $this->block->getTableName() . " AS b
369
      ON t.block_id = b.id
370
      LEFT JOIN " . $this->user->getTableName() . " AS a
371
      ON t.account_id = a.id
372
      LEFT JOIN " . $this->coin_address->getTableName() . " AS ca
0 ignored issues
show
Bug introduced by
The property coin_address does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
373
      ON ca.account_id = a.id
374
      WHERE t.archived = 0 AND ca.ap_threshold > 0 AND ca.coin_address IS NOT NULL AND ca.coin_address != '' AND ca.currency = ?
375
      GROUP BY t.account_id
376
      HAVING confirmed > ca.ap_threshold AND confirmed > " . $this->config['txfee_auto'] . "
377
      LIMIT ?");
378 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('si', $this->config['currency'], $limit) && $stmt->execute() && $result = $stmt->get_result())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
379
      return $result->fetch_all(MYSQLI_ASSOC);
380
    return $this->sqlError();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError(); (boolean) is incompatible with the return type documented by Transaction::getAPQueue of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
381
  }
382
383
384
  /**
385
   * Debit a user account
386
   * @param account_id int Account ID
387
   * @param coin_address string Coin Address
388
   * @param amount float Balance to record
389
   * @return int Debit transaction ID or false
390
   **/
391
  public function createDebitMPRecord($account_id, $coin_address, $amount) {
392
    return $this->createDebitRecord($account_id, $coin_address, $amount, 'Debit_MP');
393
  }
394
  public function createDebitAPRecord($account_id, $coin_address, $amount) {
395
    return $this->createDebitRecord($account_id, $coin_address, $amount, 'Debit_AP');
396
  }
397
  private function createDebitRecord($account_id, $coin_address, $amount, $type) {
398
    // Calculate and deduct txfee from amount
399
    $type == 'Debit_MP' ? $txfee = $this->config['txfee_manual'] : $txfee = $this->config['txfee_auto'];
400
    $amount = $amount - $txfee;
401
    // Add Debit record
402 View Code Duplication
    if (!$this->addTransaction($account_id, $amount, $type, NULL, $coin_address, NULL)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403
      $this->setErrorMessage('Failed to create ' . $type . ' transaction record in database');
404
      return false;
405
    }
406
    // Fetch the inserted record ID so we can return this at the end
407
    $transaction_id = $this->insert_id;
408
    // Add TXFee record
409 View Code Duplication
    if ($txfee > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
410
      if (!$this->addTransaction($account_id, $txfee, 'TXFee', NULL, $coin_address)) {
411
        $this->setErrorMessage('Failed to create TXFee transaction record in database: ' . $this->getError());
412
        return false;
413
      }
414
    }
415
    // Mark transactions archived
416
    if (!$this->setArchived($account_id, $this->insert_id)) {
417
      $this->setErrorMessage('Failed to mark transactions <= #' . $this->insert_id . ' as archived. ERROR: ' . $this->getError());
418
      return false;
419
    }
420
    // Recheck the users balance to make sure it is now 0
421
    if (!$aBalance = $this->getBalance($account_id)) {
422
      $this->setErrorMessage('Failed to fetch balance for account ' . $account_id . '. ERROR: ' . $this->getCronError());
423
      return false;
424
    }
425
    if ($aBalance['confirmed'] > 0) {
426
      $this->setErrorMessage('User has a remaining balance of ' . $aBalance['confirmed'] . ' after a successful payout!');
427
      return false;
428
    }
429
    // Notify user via  mail
430
    $aMailData['email'] = $this->user->getUserEmailById($account_id);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$aMailData was never initialized. Although not strictly required by PHP, it is generally a good practice to add $aMailData = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
431
    $aMailData['subject'] = $type . ' Completed';
432
    $aMailData['amount'] = $amount;
433
    $aMailData['currency'] = $this->config['currency'];
434
    if (!$this->notification->sendNotification($account_id, 'payout', $aMailData)) {
435
      $this->setErrorMessage('Failed to send notification email to users address: ' . $aMailData['email'] . 'ERROR: ' . $this->notification->getCronError());
0 ignored issues
show
Bug introduced by
The property notification does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
436
    }
437
    return $transaction_id;
438
  }
439
440
  /**
441
   * Get all new, unprocessed manual payout requests
442
   * @param none
443
   * @return data Associative array with DB Fields
444
   **/
445
  public function getMPQueue($limit=250) {
446
    $stmt = $this->mysqli->prepare("
447
      SELECT
448
      a.id,
449
      a.username,
450
      ca.ap_threshold,
451
      ca.coin_address,
452
      p.id AS payout_id,
453
      IFNULL(
454
          (
455
            SUM( IF( ( t.type IN ('Credit','Bonus') AND b.confirmations >= " . $this->config['confirmations'] . ") OR t.type = 'Credit_PPS', t.amount, 0 ) ) -
456
            SUM( IF( t.type IN ('Debit_MP', 'Debit_AP'), t.amount, 0 ) ) -
457
            SUM( IF( ( t.type IN ('Donation','Fee') AND b.confirmations >= " . $this->config['confirmations'] . ") OR ( t.type IN ('Donation_PPS', 'Fee_PPS', 'TXFee') ), t.amount, 0 ) )
458
        ), 0
459
      ) AS confirmed
460
      FROM " . $this->payout->getTableName() . " AS p
0 ignored issues
show
Bug introduced by
The property payout does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
461
      JOIN " . $this->user->getTableName() . " AS a
462
      ON p.account_id = a.id
463
      JOIN " . $this->getTableName() . " AS t
464
      ON t.account_id = p.account_id
465
      LEFT JOIN " . $this->block->getTableName() . " AS b
466
      ON t.block_id = b.id
467
      LEFT JOIN " . $this->coin_address->getTableName() . " AS ca
468
      ON ca.account_id = a.id
469
      WHERE p.completed = 0 AND t.archived = 0 AND ca.currency = ? AND ca.coin_address IS NOT NULL AND ca.coin_address != ''
470
      GROUP BY t.account_id
471
      HAVING confirmed > " . $this->config['txfee_manual'] . "
472
      LIMIT ?");
473 View Code Duplication
    if ($this->checkStmt($stmt) && $stmt->bind_param('si', $this->config['currency'], $limit) && $stmt->execute() && $result = $stmt->get_result())
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
474
      return $result->fetch_all(MYSQLI_ASSOC);
475
    return $this->sqlError('E0050');
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sqlError('E0050'); (boolean) is incompatible with the return type documented by Transaction::getMPQueue of type data.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
476
  }
477
}
478
479
$transaction = new Transaction();
480
$transaction->setMemcache($memcache);
481
$transaction->setNotification($notification);
482
$transaction->setSetting($setting);
483
$transaction->setDebug($debug);
484
$transaction->setCoin($coin);
485
$transaction->setCoinAddress($coin_address);
486
$transaction->setMysql($mysqli);
487
$transaction->setConfig($config);
488
$transaction->setBlock($block);
489
$transaction->setUser($user);
490
$transaction->setPayout($oPayout);
491
$transaction->setErrorCodes($aErrorCodes);
492