Completed
Push — master ( b67e96...89cbd7 )
by Russell
02:56
created

UpdateProofController::updateVersions()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 12
nop 0
dl 0
loc 34
rs 8.4444
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\Controller;
9
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\ORM\DB;
13
use SilverStripe\Core\ClassInfo;
14
use SilverStripe\Control\Controller;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Control\Director;
17
use SilverStripe\Versioned\Versioned;
18
use PhpTek\Verifiable\ORM\FieldType\ChainpointProof;
19
use PhpTek\Verifiable\Verify\VerifiableExtension;
20
use PhpTek\Verifiable\Exception\VerifiableValidationException;
21
22
/**
23
 * Controller available to CLI or XHR requests for updating all or selected versionable
24
 * object versions with full-proofs.
25
 *
26
 * @todo Check with Tierion API docs: How many nodes should be submitted to? And why if I submit to only one, does the network not synchronise it?
27
 * @todo Update logic to write hashes to all 3 nodes, not just the first as-is the case in the client() method.
28
 * @todo Only fetch versions that have unique proof values
29
 * @todo Call this controller from admin UI for pending verification statuses
30
 * @todo Declare a custom Monolog\Formatter\FormatterInterface and refactor log() method
31
 */
32
class UpdateProofController extends Controller
33
{
34
    /**
35
     * Entry point.
36
     *
37
     * @param  HTTPRequest $request
38
     * @throws Exception
39
     */
40
    public function index(HTTPRequest $request = null)
41
    {
42
        if (!$backend = $this->verifiableService->getBackend()->name() === 'chainpoint') {
0 ignored issues
show
Bug introduced by
The method getBackend() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

42
        if (!$backend = $this->verifiableService->/** @scrutinizer ignore-call */ getBackend()->name() === 'chainpoint') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The property verifiableService does not exist on PhpTek\Verifiable\Controller\UpdateProofController. Since you implemented __get, consider adding a @property annotation.
Loading history...
43
            throw new \Exception(sprintf('Cannot use %s backend with %s!', $backend, __CLASS__));
0 ignored issues
show
Bug introduced by
$backend of type false is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

43
            throw new \Exception(sprintf('Cannot use %s backend with %s!', /** @scrutinizer ignore-type */ $backend, __CLASS__));
Loading history...
44
        }
45
46
        $class = $request->getVar('Class') ?? '';
0 ignored issues
show
Bug introduced by
The method getVar() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

46
        $class = $request->/** @scrutinizer ignore-call */ getVar('Class') ?? '';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
47
        $recordId = $request->getVar('ID') ?? 0;
48
        $version = $request->getVar('Version') ?? 0;
49
50
        if ($class && $recordId && $version) {
51
            $this->log('NOTICE', 'Start: Processing single proof...', 2);
52
            $this->updateVersion($class, $recordId, $version);
53
        } else {
54
            $this->log('NOTICE', 'Start: Processing all proofs...', 2);
55
56
            // Get all records with partial proofs. Attempt to fetch their full proofs
57
            // from Tierion, then write them back to local DB
58
            $this->updateVersions();
59
        }
60
61
        $this->log('NOTICE', 'End.', 2);
62
    }
63
64
    /**
65
     * Process a single version for a single record. Fetch partial proof, ready to
66
     * make them whole again by re-writing to the xxx_Versions table's "Proof" field.
67
     *
68
     * @param  string $class
69
     * @param  int    $id
70
     * @param  int    $version
71
     * @return void
72
     * @throws Exception
73
     */
74
    protected function updateVersion($class, $id, $version)
75
    {
76
        if (!$record = $class::get()->byID($id)) {
77
            throw new \Exception(sprintf('Cannot find %s record for #%d', $class, $id));
78
        }
79
80
        if (!$record->hasExtension(VerifiableExtension::class)) {
81
            throw new \Exception(sprintf('%s does not have verifiable extension applied', $class));
82
        }
83
84
        $record = Versioned::get_version($class, $id, $version);
85
86
        $this->process($record);
0 ignored issues
show
Bug introduced by
The call to PhpTek\Verifiable\Contro...ofController::process() has too few arguments starting with proof. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

86
        $this->/** @scrutinizer ignore-call */ 
87
               process($record);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
87
    }
88
89
    /**
90
     * Process all versions for all applicable records. Fetch partial proofs, ready to
91
     * make them whole again by re-writing to the xxx_Versions table's "Proof" field.
92
     *
93
     * @return void
94
     */
95
    protected function updateVersions()
96
    {
97
        // Get decorated classes
98
        $dataObjectSublasses = ClassInfo::getValidSubClasses(DataObject::class);
99
100
        foreach ($dataObjectSublasses as $class) {
101
            $obj = Injector::inst()->create($class);
102
103
            if (!$obj->hasExtension(VerifiableExtension::class)) {
104
                continue;
105
            }
106
107
            $this->log('NOTICE', "Processing class: $class");
108
            $classFlag = false;
109
110
            foreach ($class::get() as $item) {
111
                $versions = Versioned::get_all_versions($class, $item->ID)->sort('Version ASC');
112
113
                foreach ($versions as $record) {
114
                    if (!$proof = $record->dbObject('Proof')) {
115
                        continue;
116
                    }
117
118
                    if ($proof->isInitial()) {
119
                        $classFlag = true;
120
                        $this->log('NOTICE', "\tInitial proof found for ID #{$record->RecordID} and version {$record->Version}");
121
                        $this->log('NOTICE', "\tRequesting proof via UUID {$proof->getHashIdNode()[0]}");
122
                        $this->process($record, $proof);
123
                    }
124
                }
125
            }
126
127
            if (!$classFlag) {
128
                $this->log('NOTICE', "Nothing to do.");
129
            }
130
        }
131
    }
132
133
    /**
134
     * Make the call to the backend, return a full-proof if it's available and
135
     * update the local version(s) with it.
136
     *
137
     * @param  DataObject $record
138
     * @param  string     $proof
139
     * @return void
140
     */
141
    protected function process($record, $proof)
142
    {
143
        // I don't understand the Tierion network... if we submit a hash to one IP
144
        // the rest of the network is not updated. So we need to pre-seed the service
145
        // with the saved nodes...unless it's only verified proofs that get propagated..??
146
        $nodes = $record->dbObject('Extra')->getStoreAsArray();
147
        $uuid = $proof->getHashIdNode()[0];
148
        $this->verifiableService->setExtra($nodes);
0 ignored issues
show
Bug Best Practice introduced by
The property verifiableService does not exist on PhpTek\Verifiable\Controller\UpdateProofController. Since you implemented __get, consider adding a @property annotation.
Loading history...
149
150
        $this->log('NOTICE', sprintf("\tCalling cached node: %s/proofs/%s", $nodes[0], $uuid));
151
152
        // Don't attempt to write anything that isn't a full proof
153
        try {
154
            $response = $this->verifiableService->call('read', $uuid);
155
        } catch (VerifiableValidationException $e) {
156
            $this->log('ERROR', $e->getMessage());
157
        }
158
159
        $isFull = ChainpointProof::create()
160
                ->setValue($response)
161
                ->isFull();
162
163
        if ($isFull) {
164
            $this->log('NOTICE', "Full proof fetched. Updating record ID #{$record->RecordID} and version {$record->Version}");
165
            $this->doUpdate($record, $record->Version, $response);
166
        } else {
167
            $this->log('WARN', "\t\tNo full proof found for record ID #{$record->RecordID} and version {$record->Version}");
168
        }
169
    }
170
171
    /**
172
     * Use the lowest level of the ORM to update the xxx_Versions table directly
173
     * with a proof.
174
     *
175
     * @param type $id
176
     * @param type $version
0 ignored issues
show
Bug introduced by
The type PhpTek\Verifiable\Controller\type 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...
177
     */
178
    protected function doUpdate($object, $version, $proof)
179
    {
180
        $table = sprintf('%s_Versions', $object->config()->get('table_name'));
181
        $sql = sprintf(
182
            'UPDATE "%s" SET "Proof" = \'%s\' WHERE "RecordID" = %d AND "Version" = %d',
183
            $table,
184
            $proof,
185
            $object->ID,
186
            $version
187
        );
188
189
        DB::query($sql);
190
191
        $this->log('NOTICE', "Version #$version of record #{$object->ID} updated.");
192
    }
193
194
    /**
195
     * Simple colourised logging for CLI operation only.
196
     *
197
     * @param  string $type
198
     * @param  string $msg
199
     * @param  int    $newLine
200
     * @return void
201
     */
202
    protected function log(string $type, string $msg, int $newLines = 1)
203
    {
204
        if (!Director::is_cli()) {
205
            return;
206
        }
207
208
        $lb = Director::is_cli() ? PHP_EOL : '<br/>';
209
        $colours = [
210
            'default' => "\033[0m",
211
            'red' => "\033[31m",
212
            'green' => "\033[32m",
213
            'yellow' => "\033[33m",
214
            'blue' => "\033[34m",
215
            'magenta' => "\033[35m",
216
        ];
217
218
        switch ($type) {
219
            case 'ERROR':
220
                $colour = $colours['red'];
221
                break;
222
            case 'WARN':
223
                $colour = $colours['yellow'];
224
                break;
225
            default:
226
            case 'WARN':
227
                $colour = $colours['green'];
228
                break;
229
        }
230
231
        echo sprintf('%s[%s] %s%s%s',
232
                $colour,
233
                $type,
234
                $msg,
235
                str_repeat($lb, $newLines),
236
                $colours['default']
237
            );
238
    }
239
240
}
241