Completed
Push — master ( 871ab4...0386fd )
by Russell
03:38
created

VerifiableExtension::onAfterPublish()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 6
nop 0
dl 0
loc 23
rs 9.4222
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
use SilverStripe\Forms\HiddenField;
17
use PhpTek\JSONText\ORM\FieldType\JSONText;
18
use SilverStripe\View\Requirements;
19
use SilverStripe\Versioned\Versioned;
20
use SilverStripe\ORM\DB;
21
22
/**
23
 * By attaching this extension to any {@link DataObject} subclass, it will therefore
24
 * be "verifiable aware". Declaring a `verify()` method on it, will automatically
25
 * make whatever the method returns, into that which is hashed and anchored to
26
 * the backend.
27
 *
28
 * If no `verify()` method is detected, the fallback is to assume that selected
29
 * fields on your data model should be combined and hashed. For this to work,
30
 * declare a `verifiable_fields` array in YML config. All subsequent publish actions
31
 * will be passed through here via {@link $this->onBeforeWrite()}.
32
 *
33
 * This {@link DataExtension} also provides a single field to which all verified
34
 * and verifiable chainpoint proofs are stored in a queryable JSON-aware field.
35
 */
36
class VerifiableExtension extends DataExtension
37
{
38
    /**
39
     * Declares a JSON-aware {@link DBField} where all chainpoint proofs are stored.
40
     *
41
     * @var array
42
     * @config
43
     */
44
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
45
        'Proof' => ChainpointProof::class,
46
        'Extra' => JSONText::class,
47
    ];
48
49
    /**
50
     * When no `verify()` method is found on decorated objects, this is the list
51
     * of fields who's values will be hashed and committed to the current backend.
52
     *
53
     * @var array
54
     * @config
55
     */
56
    private static $verifiable_fields = [];
0 ignored issues
show
introduced by
The private property $verifiable_fields is not used, and could be removed.
Loading history...
57
58
    /**
59
     * After each publish action, userland data coming from either a custom `verify()`
60
     * method or `$verifiable_fields` config, is compiled into a string, hashed and
61
     * submitted to the current backend.
62
     *
63
     * @return void
64
     */
65
    public function onAfterPublish()
66
    {
67
        $verifiable = $this->source();
68
        $owner = $this->getOwner();
69
        $this->verifiableService->setExtra();
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...
70
        $doAnchor = (count($verifiable) && $owner->exists());
71
72
        if ($doAnchor && $proofData = $this->verifiableService->call('write', $verifiable)) {
73
            if (is_array($proofData)) {
74
                $proofData = json_encode($proofData);
75
            }
76
77
            // Update the versions table manually to avoid double publish problem
78
            // when a DO is marked as "changed"
79
            $latest = Versioned::get_latest_version(get_class($owner), $owner->ID);
80
            $table = sprintf('%s_Versions', $latest->config()->get('table_name'));
81
            DB::query(sprintf(
82
                'UPDATE "%s" SET "Proof" = \'%s\', Extra = \'%s\' WHERE "RecordID" = %d AND "Version" = %d',
83
                $table,
84
                $proofData,
85
                json_encode($this->verifiableService->getExtra()),
86
                $latest->ID,
87
                $latest->Version
88
            ));
89
        }
90
    }
91
92
    /**
93
     * Source the data that will end-up hashed and submitted. This method will
94
     * call a custom verify() method on all decorated objects, if one is defined.
95
     * This provides a flexible public API for hashing and verifying pretty much
96
     * anything.
97
     *
98
     * If no such method exists, the default is to take the values of the YML
99
     * config "verifiable_fields" array, then hash and submit the values of those
100
     * fields. If no verifiable_fields are found or configured, we just return
101
     * an empty array and just stop.
102
     *
103
     * @param  DataObject $record
0 ignored issues
show
Bug introduced by
The type PhpTek\Verifiable\Verify\DataObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
104
     * @return array
105
     */
106
    public function source($record = null) : array
107
    {
108
        $record = $record ?: $this->getOwner();
109
        $verifiable = [];
110
111
        if (method_exists($record, 'verify')) {
112
            $verifiable = (array) $record->verify();
113
        } else {
114
            $fields = $record->config()->get('verifiable_fields');
115
116
            foreach ($fields as $field) {
117
                if ($field === 'Proof') {
118
                    continue;
119
                }
120
121
                $verifiable[] = (string) $record->getField($field);
122
            }
123
        }
124
125
        return $verifiable;
126
    }
127
128
    /**
129
     * Adds a "Verification" tab to the CMS.
130
     *
131
     * @param  FieldList $fields
132
     * @return void
133
     */
134
    public function updateCMSFields(FieldList $fields)
135
    {
136
        parent::updateCMSFields($fields);
137
138
        Requirements::javascript('phptek/verifiable: client/verifiable.js');
139
140
        $owner = $this->getOwner();
141
        $list = [];
142
        $versions = $owner->Versions()->sort('Version');
143
144
        foreach ($versions as $item) {
145
            $list[$item->Version] = sprintf('Version: %s (Created: %s)', $item->Version, $item->Created);
146
        }
147
148
        $fields->addFieldsToTab('Root.Verify', FieldList::create([
149
            LiteralField::create('Introduction', '<p class="message">Select a version'
150
                    . ' whose data you wish to verify, then select the "Verify"'
151
                    . ' button. After a few seconds, a verification status will be'
152
                    . ' displayed.</p>'),
153
            HiddenField::create('Type', null, get_class($owner)),
154
            DropdownField::create('Version', 'Version', $list)
155
                ->setEmptyString('-- Select One --'),
156
            FormAction::create('doVerify', 'Verify')
157
                ->setUseButtonTag(true)
158
                ->addExtraClass('btn action btn-outline-primary ')
159
        ]));
160
    }
161
162
    /**
163
     * Get the contents of this model's "Extra" field by numeric index.
164
     *
165
     * @param  int $num
166
     * @return mixed array | int
167
     */
168
    public function getExtraByIndex(int $num = null)
169
    {
170
        $extra = $this->getOwner()->dbObject('Extra');
171
        $extra->setReturnType('array');
172
173
        if (!$num) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $num of type null|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
174
            return $extra->getStoreAsArray();
175
        }
176
177
        if (!empty($value = $extra->nth($num))) {
178
            return is_array($value) ? $value[0] : $value; // <-- stuuupId. Needs fixing in JSONText
179
        }
180
181
        return [];
182
    }
183
184
}
185