Passed
Push — master ( 9ceb95...784902 )
by Russell
10:38
created

VerifiableExtension::proofExists()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 4
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @author  Russell Michell 2018 <[email protected]>
5
 * @package silverstripe-verifiable
6
 */
7
8
namespace PhpTek\Verifiable\Verify;
9
10
use SilverStripe\ORM\DataExtension;
11
use PhpTek\Verifiable\ORM\Fieldtype\ChainpointProof;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\FormAction;
14
use SilverStripe\Forms\LiteralField;
15
use SilverStripe\Forms\DropdownField;
16
17
/**
18
 * By attaching this extension to any {@link DataObject} subclass and declaring a
19
 * $verifiable_fields array in YML config, all subsequent database writes will
20
 * be passed through here via {@link $this->onAfterWrite()};
21
 *
22
 * This {@link DataExtension} also provides a single field to which all verified
23
 * and verifiable chainpoint proofs are stored in a queryable JSON-aware field.
24
 *
25
 * @todo Store the hash function used and if subsequent verifications
26
 *       fail because differing hash functions are used, throw an exception.
27
 * @todo Hard-code "Created" and "LastEdited" fields into "verifiable_fields"
28
 * @todo Prevent "Proof" field from ever being configured in verifiable_fields
29
 * @todo Use AsyncPHP to make the initial write call to the backend, wait ~15s and then request a proof in return
30
 */
31
class VerifiableExtension extends DataExtension
32
{
33
    /**
34
     * Declares a JSON-aware {@link DBField} where all chainpoint proofs are stored.
35
     *
36
     * @var array
37
     * @config
38
     */
39
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
40
        'Proof' => ChainpointProof::class,
41
    ];
42
43
    /**
44
     * The field-values that will be hashed and committed to the current backend.
45
     *
46
     * @var array
47
     * @config
48
     */
49
    private static $verifiable_fields = [];
0 ignored issues
show
introduced by
The private property $verifiable_fields is not used, and could be removed.
Loading history...
50
51
    /**
52
     * Write -->onAfterWrite() recursion prevention.
53
     *
54
     * @var string
55
     */
56
    protected static $has_write_occured = false;
57
58
    /**
59
     * After each write, data from the $verifiable_fields config is compiled
60
     * into a string, hashed and submitted to the current backend.
61
     *
62
     * Once written, we periodically poll the backend to receive the full
63
     * chainpoint proof (it takes time for Bitcoin's PoW confirmations, not so
64
     * much for Ethereum).
65
     *
66
     * We need this "complete" proof for subsequent verification checks
67
     * also made against the same backend in the future.
68
     *
69
     * If only the "Proof" field has been written-to, or no-data is found in the
70
     * verifiable_fields, this should not constitute a write that we need to do
71
     * anything with, and it's therefore skipped.
72
     *
73
     * @return void
74
     * @todo Add a check into $skipWriteProof for checking _only_ "Proof" having changed.
75
     * This should _not_ constitute a change requiting a call to the backend for.
76
     */
77
    public function onAfterWrite()
78
    {
79
        parent::onAfterWrite();
80
81
        // Overflow protection should bad recursion occur
82
        static $write_count = 0;
83
        $write_count++;
84
85
        $verifiable = $this->normaliseData();
86
        $skipWriteProof = self::$has_write_occured === true || !count($verifiable) || $write_count > 1;
87
88
        if ($skipWriteProof) {
89
            // Now we fire-off a job to check if verification is complete
90
            // TODO: Use AsyncPHP and use a callback OR check if API has a callback
91
            // endpoint it can call on our end (Tierion's API does)
92
            // $this->verifiableService->queueVerification($this->getOwner());
93
94
            return;
95
        }
96
97
        if ($proofData = $this->verifiableService->write($verifiable)) {
0 ignored issues
show
Bug introduced by
The property verifiableService does not exist on PhpTek\Verifiable\Verify\VerifiableExtension. Did you mean verifiable_fields?
Loading history...
98
            self::$has_write_occured = true;
0 ignored issues
show
Documentation Bug introduced by
The property $has_write_occured was declared of type string, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
99
100
            $this->writeProof($proofData);
101
        }
102
    }
103
104
    /**
105
     * Normalise this model's data so it's suited to being hashed.
106
     *
107
     * @return array
108
     * @todo use array_reduce()?
109
     */
110
    public function normaliseData() : array
111
    {
112
        $fields = $this->getOwner()->config()->get('verifiable_fields');
113
        $verifiable = [];
114
115
        foreach ($fields as $field) {
116
            $verifiable[] = (string) $this->getOwner()->getField($field);
117
        }
118
119
        return $verifiable;
120
    }
121
122
    /**
123
     * Writes string data that is assumed to be JSON (as returned from a
124
     * web-service for example) and saved to this decorated object's "Proof"
125
     * field.
126
     *
127
     * @param  mixed $proof
128
     * @return void
129
     */
130
    public function writeProof($proof)
131
    {
132
        if (is_array($proof)) {
133
            $proof = json_encode($proof);
134
        }
135
136
        $owner = $this->getOwner();
137
        $owner->setField('Proof', $proof);
138
        $owner->write();
139
    }
140
141
    /**
142
     * Adds a "Verification" tab to the CMS.
143
     *
144
     * @param  FieldList $fields
145
     * @return void
146
     */
147
    public function updateCMSFields(FieldList $fields)
148
    {
149
        parent::updateCMSFields($fields);
150
151
        $owner = $this->getOwner();
152
        $list = $owner->Versions();
153
154
        $fields->addFieldsToTab('Root.Verify', FieldList::create([
155
            LiteralField::create('Introduction', '<p class="vry-intro"></p>'),
156
            DropdownField::create('Version', 'Version', $list->toArray())
157
                ->setEmptyString('-- Select One --'),
158
                FormAction::create('doVerify', 'Verify')
159
        ]));
160
    }
161
162
}
163