Completed
Push — master ( 5a833b...970589 )
by Russell
02:34
created

UpdateProofController::index()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 28
rs 8.8333
c 0
b 0
f 0
cc 7
nc 4
nop 1
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\Model\VerifiableExtension;
20
use PhpTek\Verifiable\Exception\VerifiableValidationException;
21
use SilverStripe\Security\Permission;
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 Best Practice introduced by
The property service does not exist on PhpTek\Verifiable\Controller\UpdateProofController. Since you implemented __get, consider adding a @property annotation.
Loading history...
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...
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('NOTICE', "\tInitial proof found for ID #{$record->RecordID} and version {$record->Version}");
132
                        $this->log('NOTICE', "\tRequesting proof via UUID {$proof->getHashIdNode()[0]}");
133
                        $this->process($record, $proof);
134
                    }
135
                }
136
            }
137
138
            if (!$classFlag) {
139
                $this->log('NOTICE', "Nothing to do.");
140
            }
141
        }
142
    }
143
144
    /**
145
     * Make the call to the backend, return a full-proof if it's available and
146
     * update the local version(s) with it.
147
     *
148
     * @param  DataObject $record
149
     * @param  string     $proof
150
     * @return void
151
     */
152
    protected function process($record, $proof)
153
    {
154
        $uuid = $proof->getHashIdNode()[0];
155
        // Pre-seed the service with the saved nodes...unless it's only verified proofs that get propagated..??
156
        $nodes = $record->dbObject('Extra')->getStoreAsArray();
157
        $this->service->setExtra($nodes);
0 ignored issues
show
Bug Best Practice introduced by
The property service does not exist on PhpTek\Verifiable\Controller\UpdateProofController. Since you implemented __get, consider adding a @property annotation.
Loading history...
158
159
        $this->log('NOTICE', sprintf("\tCalling cached node: %s/proofs/%s", $nodes[0], $uuid));
160
161
        // Don't attempt to write anything that isn't a full proof
162
        try {
163
            $response = $this->service->call('read', $uuid);
164
        } catch (VerifiableValidationException $e) {
165
            $this->log('ERROR', $e->getMessage());
166
167
            return;
168
        }
169
170
        $proof = ChainpointProof::create()
171
                ->setValue($response);
172
173
        if ($proof && $proof->isFull()) {
174
            $this->log('NOTICE', "Full proof fetched. Updating record ID #{$record->RecordID} and version {$record->Version}");
175
            $this->doUpdate($record, $record->Version, $response);
176
        } else {
177
            $this->log('WARN', "\t\tNo full proof found for record ID #{$record->RecordID} and version {$record->Version}");
178
        }
179
    }
180
181
    /**
182
     * Use the lowest level of the ORM to update the xxx_Versions table directly
183
     * with a proof.
184
     *
185
     * @param type $id
186
     * @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...
187
     */
188
    protected function doUpdate($object, $version, $proof)
189
    {
190
        $table = sprintf('%s_Versions', $object->baseTable());
191
        $sql = sprintf(
192
            'UPDATE "%s" SET "Proof" = \'%s\' WHERE "RecordID" = %d AND "Version" = %d',
193
            $table,
194
            $proof,
195
            $object->ID,
196
            $version
197
        );
198
199
        DB::query($sql);
200
201
        $this->log('NOTICE', "Version #$version of record #{$object->ID} updated.");
202
    }
203
204
    /**
205
     * Simple colourised logging for CLI operation only.
206
     *
207
     * @param  string $type
208
     * @param  string $msg
209
     * @param  int    $newLine
210
     * @return void
211
     */
212
    protected function log(string $type, string $msg, int $newLines = 1)
213
    {
214
        if (!Director::is_cli()) {
215
            return;
216
        }
217
218
        $lb = Director::is_cli() ? PHP_EOL : '<br/>';
219
        $colours = [
220
            'default' => "\033[0m",
221
            'red' => "\033[31m",
222
            'green' => "\033[32m",
223
            'yellow' => "\033[33m",
224
            'blue' => "\033[34m",
225
            'magenta' => "\033[35m",
226
        ];
227
228
        switch ($type) {
229
            case 'ERROR':
230
                $colour = $colours['red'];
231
                break;
232
            case 'WARN':
233
                $colour = $colours['yellow'];
234
                break;
235
            default:
236
            case 'WARN':
237
                $colour = $colours['green'];
238
                break;
239
        }
240
241
        echo sprintf('%s[%s] %s%s%s',
242
                $colour,
243
                $type,
244
                $msg,
245
                str_repeat($lb, $newLines),
246
                $colours['default']
247
            );
248
    }
249
250
}
251