UpdateProofController   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 106
dl 0
loc 229
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
B index() 0 28 7
A updateVersion() 0 13 3
A process() 0 31 4
B log() 0 36 6
B updateVersions() 0 46 9
A doUpdate() 0 14 1
1
<?php
2
3
/**
4
 * @author  Russell Michell 2018 <[email protected]>
5
 * @package silverstripe-verifiable
6
 */
7
8
namespace PhpTek\Verifiable\Control;
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 SilverStripe\Security\Permission;
19
use PhpTek\Verifiable\ORM\FieldType\ChainpointProof;
20
use PhpTek\Verifiable\Extension\VerifiableExtension;
21
use PhpTek\Verifiable\Exception\VerifiableValidationException;
22
23
/**
24
 * Controller available to CLI or XHR requests for updating all or selected versionable
25
 * object versions with full-proofs.
26
 *
27
 * @todo Check with Chainpoint API docs: How many nodes should be submitted to?
28
 * @todo Only fetch versions that have unique proof values
29
 * @todo Declare a custom Monolog\Formatter\FormatterInterface and refactor log() method
30
 */
31
class UpdateProofController extends Controller
32
{
33
    /**
34
     * Entry point.
35
     *
36
     * @param  HTTPRequest $request
37
     * @throws Exception
38
     */
39
    public function index(HTTPRequest $request = null)
40
    {
41
        if (!$backend = $this->service->name() === 'chainpoint') {
0 ignored issues
show
Bug introduced by
The method name() 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

41
        if (!$backend = $this->service->/** @scrutinizer ignore-call */ 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 service does not exist on PhpTek\Verifiable\Control\UpdateProofController. Since you implemented __get, consider adding a @property annotation.
Loading history...
42
            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

42
            throw new \Exception(sprintf('Cannot use %s backend with %s!', /** @scrutinizer ignore-type */ $backend, __CLASS__));
Loading history...
43
        }
44
45
        if (!Director::is_cli() && !Permission::check('ADMIN')) {
46
            throw new \Exception('You do not have permission to access this controller');
47
        }
48
49
        $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

49
        $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...
50
        $recordId = $request->getVar('ID') ?? 0;
51
        $version = $request->getVar('Version') ?? 0;
52
53
        // Process one specific record + version
54
        if ($class && $recordId && $version) {
55
            $this->log('NOTICE', 'Start: Processing single proof...', 2);
56
            $this->updateVersion($class, $recordId, $version);
57
        // Process everything
58
        } else {
59
            $this->log('NOTICE', 'Start: Processing all proofs...', 2);
60
61
            // Get all records with partial proofs. Attempt to fetch their full proofs
62
            // from Tierion, then write them back to local DB
63
            $this->updateVersions();
64
        }
65
66
        $this->log('NOTICE', 'End.', 2);
67
    }
68
69
    /**
70
     * Process a single version for a single record. Fetch partial proof, ready to
71
     * make them whole again by re-writing to the xxx_Versions table's "Proof" field.
72
     *
73
     * @param  string $class
74
     * @param  int    $id
75
     * @param  int    $version
76
     * @return void
77
     * @throws Exception
78
     */
79
    protected function updateVersion($class, $id, $version)
80
    {
81
        if (!$record = $class::get()->byID($id)) {
82
            throw new \Exception(sprintf('Cannot find %s record for #%d', $class, $id));
83
        }
84
85
        if (!$record->hasExtension(VerifiableExtension::class)) {
86
            throw new \Exception(sprintf('%s does not have verifiable extension applied', $class));
87
        }
88
89
        $record = Versioned::get_version($class, $id, $version);
90
91
        $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

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