Completed
Push — develop ( f2bd12...f57c6e )
by Seth
06:57
created

CanvasAPIviaLTI_Installer::createSecretsFile()   D

Complexity

Conditions 14
Paths 8

Size

Total Lines 110
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 3 Features 0
Metric Value
cc 14
eloc 60
c 8
b 3
f 0
nc 8
nop 1
dl 0
loc 110
rs 4.9516

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/** CanvasAPIviaLTI_Installer and related classes */
4
5
use Battis\AppMetadata as AppMetadata;
6
use Battis\BootstrapSmarty\NotificationMessage;
7
8
/**
9
 * Manage the installation of the LTI application on a LAMP stack
10
 *
11
 * @author Seth Battis <[email protected]>
12
 **/	
13
class CanvasAPIviaLTI_Installer {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
14
	const SECRETS_NEEDED_STEP = 1;
15
	const SECRETS_ENTERED_STEP = 2;
16
	const API_DECISION_NEEDED_STEP = 3;
17
	const API_DECISION_ENTERED_STEP = 4;
18
	const API_TOKEN_PROVIDED_STEP = 5;
19
		
20
	/**
21
	 * Generate a SECRETS_FILE from user input.
22
	 *
23
	 * @param scalar $step optional Where are we in the SECRETS_FILE creation workflow? (defaults to SECRETS_NEEDED_STEP -- the beginning)
24
	 *
25
	 * @throws CanvasAPIviaLTI_Installer_Exception If form submission does not contain all required MySQL credentals (host, username, password and database)
26
	 * @throws CanvasAPIviaLTI_Installer_Exception If SECRETS_FILE cannot be created
27
	 * @throws CanvasAPIviaLTI_Installer_Exception If $step is not a pre-defined *_STEP constant
28
	 **/
29
	public static function createSecretsFile($step = self::SECRETS_NEEDED_STEP) {
0 ignored issues
show
Coding Style introduced by
createSecretsFile uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
createSecretsFile uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
30
		global $smarty; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
31
		
32
		switch ($step) {
33
			case self::SECRETS_NEEDED_STEP: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
34
				// FIXME:0 passwords in clear text? oy. issue:17
35
				$smarty->assign('content', '
36
					<form action="' . $_SERVER['PHP_SELF'] . '" method="post">
37
						<section>
38
							<h3>Application Information</h3>
39
							<label for="name">App name <input type="text" name="name" id="name" /></label>
40
							<label for="id">App ID <input type="text" name="id" id="id" /></label>
41
							<label for="admin_username">App admin Username <input type="text" name="admin_username" id="admin_username" /></label>
42
							<label for="admin_password">App admin Password <input type="text" name="admin_password" id="admin_password" /></label>
43
						</section>
44
						<section>
45
							<h3>MySQL Connection</h3>
46
							<label for="host">Host <input type="text" name="host" id="host" value="localhost" /></label>
47
							<label for="username">Username <input type="text" name="username" id="username" /></label>
48
							<label for="password">Password <input type="text" name="password" id="password" /></label>
49
							<label for="database">Database <input type="text" name="database" id="database" /></label>
50
						</section>
51
						<section>
52
							<h3>Canvas Developer Credentials</h3>
53
							<label for="oauth_id">OAuth Client ID <input type="text" name="oauth_id" id="oauth_id" /></label>
54
							<label for="oauth_key">OAuth Client Key <input type="text" name="oauth_key" id="oauth_key" /></label>
55
						</section>
56
						<input type="hidden" name="step" value="' . self::SECRETS_ENTERED_STEP . '" />
57
						<input type="submit" value="Create Secrets File" />
58
					</form>
59
				');
60
				$smarty->display();
61
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method createSecretsFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
62
			}
63
			
64
			case self::SECRETS_ENTERED_STEP: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
65
				if (isset($_REQUEST['name']) && isset($_REQUEST['id']) && isset($_REQUEST['admin_username']) && isset($_REQUEST['admin_password'])) {
66
					if (isset($_REQUEST['host']) && isset($_REQUEST['username']) && isset($_REQUEST['password']) && isset($_REQUEST['database'])) {
67
						$secrets = new SimpleXMLElement('<secrets />');
68
						$app = $secrets->addChild('app');
69
						$app->addChild('name', $_REQUEST['name']);
70
						$app->addChild('id', $_REQUEST['id']);
71
						$admin = $app->addChild('admin');
72
						$admin->addChild('username', $_REQUEST['admin_username']);
73
						$admin->addChild('password', $_REQUEST['admin_password']);
74
						$mysql = $secrets->addChild('mysql');
75
						$mysql->addChild('host', $_REQUEST['host']);
76
						$mysql->addChild('username', $_REQUEST['username']);
77
						$mysql->addChild('password', $_REQUEST['password']);
78
						$mysql->addChild('database', $_REQUEST['database']);
79
						$oauth = $secrets->addChild('oauth');
80
						$oauth->addChild('id', $_REQUEST['oauth_id']);
81
						$oauth->addChild('key', $_REQUEST['oauth_key']);
82
						if ($secrets->asXML(SECRETS_FILE) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
83
							throw new CanvasAPIviaLTI_Exception(
84
								'Failed to create ' . SECRETS_FILE,
85
								CanvasAPIviaLTI_Installer_Exception::SECRETS_FILE_CREATION
86
							);
87
						}
88
						
89
						$htpasswdFile = __DIR__ . '/.htpasswd';
90
						shell_exec("htpasswd -bc $htpasswdFile {$_REQUEST['admin_username']} {$_REQUEST['admin_password']}");
91
						if (!file_exists($htpasswdFile)) {
92
							throw new CanvasAPIviaLTI_Installer_Exception(
93
								"Failed to create $htpasswdFile",
94
								CanvasAPIviaLTI_Installer_Exception::HTPASSWD_FILE
95
							);
96
						}
97
						
98
						$htaccessFile = __DIR__ . '/.htaccess';
99
						if(!file_put_contents($htaccessFile, "AuthType Basic\nAuthName \"{$secrets->app->name} Admin\"\nAuthUserFile $htpasswdFile\nRequire valid-user\n")) {
100
							throw new CanvasAPIviaLTI_Installer_Exception(
101
								"Failed to create $htaccessFile",
102
								CanvasAPIviaLTI_Installer_Exception::HTACCESS_FILE
103
							);
104
						}
105
					} else {
106
						throw new CanvasAPIviaLTI_Installer_Exception(
107
							'Missing a required mysql credential (host, username, password and database all required).',
108
							CanvasAPIviaLTI_Installer_Exception::SECRETS_FILE_MYSQL
109
						);
110
					}
111
					$smarty->addMessage(
112
						'Secrets file created',
113
						"<code>secrets.xml</code> contains your authentication credentials and
114
						 should be carefully protected. Be sure not to commit it to a public
115
						 repository!",
116
						NotificationMessage::GOOD
0 ignored issues
show
Deprecated Code introduced by
The constant Battis\BootstrapSmarty\NotificationMessage::GOOD has been deprecated with message: Use `SUCCESS` instead for consistency with Bootstrap

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
117
					);
118
				} else {
119
					throw new CanvasAPIviaLTI_Installer_Exception(
120
						'Missing a required app identity (name, id, admin username and admin password all required).',
121
						CanvasAPIviaLTI_Installer_Exception::SECRETS_FILE_APP
122
					);
123
				}
124
				
125
				/* clear the processed step */
126
				unset($_REQUEST['step']);
127
128
				break;
129
			}
130
			
131
			default: {
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
132
				throw new CanvasAPIviaLTI_Installer_Exception(
133
					"Unknown step ($step) in SECRETS_FILE creation.",
134
					CanvasAPIviaLTI_Installer_Exception::SECRETS_NEEDED_STEP
135
				);
136
			}
137
		}
138
	}
139
	
140
	/**
141
	 * Create database tables to back LTI_Tool_Provider
142
	 *
143
	 * @throws CanvasAPIviaLTI_Installer_Exception If database schema not found in vendors directory
144
	 * @throws CanvasAPIviaLTI_Installer_Exception If database tables are not created
145
	 **/
146
	public static function createLTIDatabaseTables() {
147
		global $sql; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
148
		global $smarty; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
149
		
150
		$ltiSchema = realpath(__DIR__ . '/../vendor/spvsoftwareproducts/LTI_Tool_Provider/lti-tables-mysql.sql');
151
		
152
		if ($sql->query("SHOW TABLES LIKE 'lti_%'")->num_rows >= 5) {
153
			$smarty->addMessage('LTI database tables exist', 'Database tables to support the LTI Tool Provider (TP) already exist and have not been re-created.');
154
		} elseif (file_exists($ltiSchema)) {
155
			$queries = explode(";", file_get_contents($ltiSchema));
156
			$created = true;
0 ignored issues
show
Unused Code introduced by
$created is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
157
			foreach($queries as $query) {
158
				if (!empty(trim($query))) {
159
					if (!$sql->query($query)) {
160
						throw new CanvasAPIviaLTI_Installer_Exception(
161
							"Error creating LTI database tables: {$sql->error}",
162
							CanvasAPIviaLTI_Installer_Exception::LTI_PREPARE_DATABASE
163
						);
164
					}
165
				}
166
			}
167
			
168
			$smarty->addMessage(
169
				'LTI database tables created',
170
				'Database tables to support the LTI Tool Provider (TP) have been created in
171
				 your MySQL database.',
172
				NotificationMessage::GOOD
0 ignored issues
show
Deprecated Code introduced by
The constant Battis\BootstrapSmarty\NotificationMessage::GOOD has been deprecated with message: Use `SUCCESS` instead for consistency with Bootstrap

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
173
			);
174
		} else {
175
			throw new CanvasAPIviaLTI_Exception("$ltiSchema not found.");
176
		}
177
	}
178
	
179
	/**
180
	 * Create database tables to back app
181
	 *
182
	 * @throws CanvasAPIviaLTI_Installer_Exception If database tables are not created
183
	 **/
184
	public static function createAppDatabaseTables() {
185
		global $sql; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
186
		global $smarty; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
187
		
188
		if (file_exists(SCHEMA_FILE)) {
189
			$queries = explode(";", file_get_contents(SCHEMA_FILE));
190
			$created = true;
191
			foreach ($queries as $query) {
192
				if (!empty(trim($query))) {
193
					if (preg_match('/CREATE\s+TABLE\s+(`([^`]+)`|\w+)/i', $query, $tableName)) {
194
						$tableName = (empty($tableName[2]) ? $tableName[1] : $tableName[2]);
195
						if ($sql->query("SHOW TABLES LIKE '$tableName'")->num_rows > 0) {
196
							$created = false;
197
						} else {
198
							if (!$sql->query($query)) {
199
								throw new CanvasAPIviaLTI_Installer_Exception(
200
									"Error creating app database tables: {$sql->error}",
201
									CanvasAPIviaLTI_Installer_Exception::APP_CREATE_TABLE
202
								);
203
							}
204
						}
205
					} else {
206
						if (!$sql->query($query)) {
207
							throw new CanvasAPIviaLTI_Installer_Exception(
208
								"Error creating app database tables: {$sql->error}",
209
								CanvasAPIviaLTI_Installer_Exception::APP_PREPARE_DATABASE
210
							);
211
						}
212
					}
213
				}
214
			}
215
			
216
			if ($created) {
217
				$smarty->addMessage(
218
					'App database tables created',
219
					'Database tables to support the application have been created in your
220
					 MySQL database.',
221
					NotificationMessage::GOOD
0 ignored issues
show
Deprecated Code introduced by
The constant Battis\BootstrapSmarty\NotificationMessage::GOOD has been deprecated with message: Use `SUCCESS` instead for consistency with Bootstrap

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
222
				);
223
			} else {
224
				$smarty->addMessage(
225
					'App database tables exist',
226
					'Database tables to support the application already exist and have not
227
					 been re-created.'
228
				);
229
			}
230
		}
231
	}
232
	
233
	/**
234
	 * Initialize the app metadata store, especially the APP_PATH and APP_URL
235
	 *
236
	 * @return AppMetadata
237
	 **/
238
	public static function createAppMetadata() {
0 ignored issues
show
Coding Style introduced by
createAppMetadata uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
239
		global $secrets; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
240
		global $sql; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
241
		global $metadata; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
242
		global $smarty; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
243
				
244
		$metadata = initAppMetadata();
245
		$metadata['APP_PATH'] = preg_replace('/\/classes$/', '', __DIR__);
246
		$metadata['APP_URL'] = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on' ? 'http://' : 'https://') . $_SERVER['SERVER_NAME'] . preg_replace("|^{$_SERVER['DOCUMENT_ROOT']}(.*)$|", '$1', $metadata['APP_PATH']);
247
		$metadata['APP_NAME'] = (string) $secrets->app->name;
248
		$metadata['APP_ID'] = (string) $secrets->app->id;
249
		$metadata['CANVAS_INSTANCE_URL_PLACEHOLDER'] = 'https://canvas.instructure.com';
250
		$smarty->assign('metadata', $metadata);
251
252
		$smarty->addMessage(
253
			'App metadata initialized',
254
			'Basic application metadata has been updated, including APP_PATH and APP_URL',
255
			NotificationMessage::GOOD
0 ignored issues
show
Deprecated Code introduced by
The constant Battis\BootstrapSmarty\NotificationMessage::GOOD has been deprecated with message: Use `SUCCESS` instead for consistency with Bootstrap

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
256
		);
257
		
258
		return $metadata;
259
	}
260
	
261
	/**
262
	 * Obtain a Canvas API token, if needed.
263
	 *
264
	 * @param scalar $step optional Where are we in the API token negotiation workflow? (defaults to API_DECISION_NEEDED_STEP -- the beginning)
265
	 * @param boolean $skip optional Skip this step (defaults to FALSE)
266
	 *
267
	 * @throws CanvasAPIviaLTI_Installer_Exception If $step is not a pre-defined *_STEP constant
268
	 **/
269
	public static function acquireAPIToken($step = self::API_DECISION_NEEDED_STEP, $skip = false) {
0 ignored issues
show
Coding Style introduced by
acquireAPIToken uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
acquireAPIToken uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
270
		global $secrets; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
271
		global $metadata; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
272
		global $smarty; // FIXME:0 grown-ups don't program like this issue:17
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
273
		
274
		if ($skip) {
275
			if (isset($metadata['CANVAS_API_TOKEN']) || isset($metadata['CANVAS_API_USER'])) {
276
				$api = new CanvasPest("{$metadata['CANVAS_INSTANCE_URL']}/login/oauth2", $metadata['CANVAS_API_TOKEN']);
277
				$api->delete('token');
278
				unset($metadata['CANVAS_API_TOKEN']);
279
				unset($metadata['CANVAS_API_USER']);
280
				$smarty->addMessage(
281
					'Existing admin Canvas API token information expunged',
282
					'There was already an administrative access token stored in your
283
					 application metadata, and it has now been expunged.'
284
				);
285
			} else {
286
				$smarty->addMessage(
287
					'No admin Canvas API token acquired',
288
					'An administrative API token has not been acquired. Users will be asked to
289
					 acquire their own API tokens on their first use of the LTI.'
290
				);
291
			}
292
		} else {
293
			switch ($step) {
294
				case self::API_DECISION_NEEDED_STEP: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
295
					$smarty->assign('content', '
296
						<form action="' . $metadata['APP_URL'] . '/admin/oauth.php" method="post">
297
							<label for="url"> Canvas Instance URL <input type="text" name="url" id="url" placeholder="' . $metadata['CANVAS_INSTANCE_URL_PLACEHOLDER'] . '" value="' . (isset($metadata['CANVAS_INSTANCE_URL']) ? $metadata['CANVAS_INSTANCE_URL'] : '') . '" /></label>
298
							<label for="token"> API Access Token <input type="text" name="token" id="token" placeholder="Leave blank to acquire a token interactively" /></label>
299
							<input type="hidden" name="skip" value="0" />
300
							<input type="hidden" name="step" value="' . self::API_DECISION_ENTERED_STEP . '" />
301
							<input type="submit" value="Use administrative token" />
302
						</form>
303
						or
304
						<form action="' . $_SERVER['PHP_SELF'] . '" method="post">
305
							<input type="hidden" name="skip" value="1" />
306
							<input type="hidden" name="step" value="' . self::API_DECISION_ENTERED_STEP . '" />
307
							<input type="submit" value="Require users to acquire individual tokens" />
308
						</form>
309
					');
310
					$smarty->display();
311
					exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method acquireAPIToken() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
312
				}
313
				case self::API_DECISION_ENTERED_STEP: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
314
					$oauth = new OAuthNegotiator();
315
					
316
					if ($oauth->isAPIToken()) {
317
						$metadata['CANVAS_API_TOKEN'] = $oauth->getToken();
318
						
319
						$smarty->addMessage(
320
							'Admin Canvas API token acquired',
321
							'An administrative API access token has been acquired and stored in your application metadata.',
322
							NotificationMessage::GOOD
0 ignored issues
show
Deprecated Code introduced by
The constant Battis\BootstrapSmarty\NotificationMessage::GOOD has been deprecated with message: Use `SUCCESS` instead for consistency with Bootstrap

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
323
						);
324
					}
325
					
326
					/* clear the processed step */
327
					unset($_REQUEST['step']);
328
					
329
					break;
330
				}
331
				case self::API_TOKEN_PROVIDED_STEP: {
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
332
					$smarty->addMessage(
333
						'Admin Canvas API token provided',
334
						'You provided an API access token and it has been stored in your application metadata.' 
335
					);
336
					break;
337
				}
338
				default: {
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
339
					throw new CanvasAPIviaLTI_Installer_Exception(
340
						"Unknown step ($step) in obtaining API token.",
341
						CanvasAPIviaLTI_Installer_Exception::API_STEP_MISMATCH
342
					);
343
				}
344
			}
345
		}
346
	}
347
}
348
349
/**
350
 * Exceptions thrown by CanvasAPIviaLTI_Installer
351
 *
352
 * @author Seth Battis <[email protected]>
353
 **/
354
class CanvasAPIviaLTI_Installer_Exception extends CanvasAPIviaLTI_Exception {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
355
	const SECRETS_FILE_CREATION = 1;
356
	const SECRETS_FILE_APP = 2;
357
	const SECRETS_FILE_MYSQL = 3;
358
	const LTI_SCHEMA = 4;
359
	const LTI_PREPARE_DATABASE = 5;
360
	const LTI_CREATE_TABLE = 6;
361
	const APP_SCHEMA = 7;
362
	const APP_PREPARE_DATABASE = 8;
363
	const APP_CREATE_TABLE = 9;
364
	const API_STEP_MISMATCH = 10;
365
	const API_URL = 14;
366
	const API_TOKEN = 11;
367
	const HTPASSWD_FILE = 12;
368
	const HTACCESS_FILE = 13;
369
}
370
371
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...