Skip to content

Commit fbc14ad

Browse files
committed
Refactor and organize new framework modules
0 parents  commit fbc14ad

File tree

1,100 files changed

+84306
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,100 files changed

+84306
-0
lines changed

admin/index.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
namespace Tangible\Admin;

admin/notice/admin-notice.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
jQuery(document).ready(function($) {
2+
3+
$('.notice.is-dismissible').each(function() {
4+
5+
var $el = $(this)
6+
var key = $el.data('tangibleAdminNotice')
7+
if (!key) return
8+
9+
// Event handler on notice itself, not button
10+
$el.on('click', '.notice-dismiss', function(e) {
11+
12+
e.preventDefault()
13+
14+
// See admin-notices/enqueue
15+
16+
var data = {
17+
action: 'tangible_dismiss_admin_notice',
18+
admin_notice_key: key,
19+
nonce: window.tangibleAdminNotice.nonce
20+
}
21+
22+
$.post(window.ajaxurl, data)
23+
24+
})
25+
})
26+
})

admin/notice/enqueue.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
add_action('admin_enqueue_scripts', function () use ($framework) {
4+
5+
if (!$framework->is_latest_version()) return;
6+
7+
$url = $framework->url;
8+
$version = $framework->version;
9+
10+
$js = $url.'/assets/admin-notices.js';
11+
12+
wp_enqueue_script('tangible-admin-notices-js', $js, ['jquery'], $version);
13+
14+
wp_localize_script(
15+
'tangible-admin-notices-js',
16+
'tangibleAdminNotice',
17+
[
18+
'nonce' => wp_create_nonce('tangible-dismiss-admin-notice')
19+
]
20+
);
21+
22+
});
23+
24+
add_action('wp_ajax_tangible_dismiss_admin_notice', function() use ($framework) {
25+
26+
if (!$framework->is_latest_version()) return;
27+
28+
check_ajax_referer('tangible-dismiss-admin-notice', 'nonce');
29+
$name = sanitize_text_field($_POST['admin_notice_key']);
30+
$framework->dismiss_admin_notice($name);
31+
exit;
32+
});

admin/notice/fields.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
$framework->get_admin_notice_setting_key = function($name) use ($framework) {
4+
return "tangible_admin_notice_dismissed__{$name}";
5+
};
6+
7+
$framework->is_admin_notice_dismissed = function($name) use ($framework) {
8+
9+
$key = $framework->get_admin_notice_setting_key($name);
10+
$value = \is_multisite()
11+
? get_network_option(null, $key, false)
12+
: get_option($key, false);
13+
14+
return !empty($value) && $value==='true';
15+
};
16+
17+
$framework->dismiss_admin_notice = function($name, $value = 'true') use ($framework) {
18+
19+
$key = $framework->get_admin_notice_setting_key($name);
20+
21+
return \is_multisite()
22+
? update_network_option(null, $key, $value)
23+
: update_option($key, $value);
24+
};
25+
26+
$framework->reset_admin_notice = function($name) use ($framework) {
27+
return $framework->dismiss_admin_notice($name, 'false');
28+
};

admin/notice/index.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
include __DIR__.'/enqueue.php';
4+
include __DIR__.'/fields.php';
5+
6+
$framework->register_admin_notices = function($plugin) use ($framework) {
7+
8+
if (!isset($plugin['admin_notice'])) return;
9+
10+
$action = $framework->is_multisite($plugin)
11+
? 'network_admin_notices'
12+
: 'admin_notices';
13+
14+
add_action($action, function() use ($plugin) {
15+
$plugin['admin_notice']($plugin);
16+
});
17+
};

api/action.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Tangible\API;
4+
5+
use Tangible\API as api;
6+
7+
api::$state->actions = [];
8+
api::$state->action_key = 'tangible_api';
9+
10+
// Action
11+
12+
function add_action($name, $callback) {
13+
api::$state->actions[ $name ] = $callback;
14+
}
15+
16+
function remove_action($name) {
17+
unset(api::$state->actions[ $name ]);
18+
}
19+
20+
function action($name, $data = []) {
21+
22+
if (!isset(api::$state->actions[ $name ])) {
23+
throw new Exception("Action not found: {$action}");
24+
}
25+
26+
return call_user_func(api::$state->actions[ $name ], $data);
27+
}
28+
29+
function get_action_key() {
30+
return api::$state->action_key;
31+
}

api/ajax.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Tangible\API;
4+
5+
use Tangible\API as api;
6+
7+
// AJAX
8+
9+
function ajax_callback() {
10+
11+
try {
12+
13+
if (!api\verify_nonce()) return api\error('Not allowed');
14+
15+
$request = isset($_POST['request']) ? json_decode(
16+
stripslashes_deep($_POST['request']),
17+
JSON_OBJECT_AS_ARRAY
18+
) : [];
19+
20+
if ( !is_array($request) ) $request = [];
21+
22+
$name = $request['action'];
23+
$data = $request['data'];
24+
25+
/**
26+
* Action callback can:
27+
*
28+
* - Call api\send() or api\error(), which outputs JSON
29+
* response and exits
30+
* - Return a result to be sent
31+
* - Throw an error
32+
*
33+
* All are standardized to response as { data } or { error }.
34+
*/
35+
$result = api\action( $name, $data );
36+
37+
return api\send( $result );
38+
39+
} catch (\Throwable $th) {
40+
41+
return api\error( $th->getMessage() );
42+
}
43+
}
44+
45+
// Logged-in users
46+
add_action(
47+
'wp_ajax_' . api::$state->action_key,
48+
__NAMESPACE__ . '\\ajax_callback'
49+
);
50+
51+
// Public
52+
add_action(
53+
'wp_ajax_nopriv_' . api::$state->action_key,
54+
__NAMESPACE__ . '\\ajax_callback'
55+
);

api/build/api.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/build/api.min.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/enqueue.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Tangible\API;
4+
5+
use Tangible\API as api;
6+
7+
// Client
8+
9+
function enqueue() {
10+
11+
$url = api::$state->url;
12+
$version = api::$state->version;
13+
14+
wp_enqueue_script(
15+
'tangible-api',
16+
$url . '/build/api.min.js',
17+
[],
18+
$version,
19+
true
20+
);
21+
22+
wp_localize_script( 'tangible-api', 'Tangible.API', [
23+
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
24+
'nonce' => api\create_nonce(),
25+
'ajaxAction' => api\get_action_key()
26+
]);
27+
}

api/index.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
namespace Tangible;
3+
use Tangible\API as api;
4+
5+
class API {
6+
static $state;
7+
}
8+
9+
api::$state = (object) [];
10+
11+
require_once __DIR__.'/action.php';
12+
require_once __DIR__.'/ajax.php';
13+
require_once __DIR__.'/response.php';

api/nonce.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
namespace Tangible\API;
3+
use Tangible\API as api;
4+
5+
// Nonce
6+
7+
function create_nonce() {
8+
return wp_create_nonce(
9+
api\get_action_key()
10+
);
11+
};
12+
13+
function verify_nonce() {
14+
return isset($_POST['nonce'])
15+
&& wp_verify_nonce($_POST['nonce'], api\get_action_key())
16+
;
17+
};

api/readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# API module
2+
3+
The API module replaces the AJAX module in the plugin framework with a new
4+
interface using browser-native `fetch()` instead of `jQuery.ajax()`.
5+
6+
First used in admin settings form, but designed to work with the Form module.

api/response.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
namespace Tangible\API;
3+
use Tangible\API as api;
4+
5+
const ok = 200;
6+
const bad_request = 400;
7+
const not_found = 404;
8+
const forbidden = 403;
9+
10+
// Response
11+
12+
function send($data = []) {
13+
14+
status_header( api\ok );
15+
16+
echo json_encode([ 'data' => $data ]);
17+
exit;
18+
};
19+
20+
function error($data = []) {
21+
22+
$error = is_string($data) ? [ 'message' => $data ] : $data;
23+
24+
status_header( $error['code'] ?? api\bad_request );
25+
26+
echo json_encode([ 'error' => $error ]);
27+
exit;
28+
};

api/src/action.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
export function createAction(options) {
3+
4+
const {
5+
ajaxUrl,
6+
nonce,
7+
ajaxAction
8+
} = options
9+
10+
return async function action(name, data) {
11+
12+
const formData = new FormData()
13+
14+
formData.append('action', ajaxAction)
15+
formData.append('nonce', nonce)
16+
formData.append('request', JSON.stringify({
17+
action: name,
18+
data
19+
}))
20+
21+
// TODO: Support file upload
22+
23+
try {
24+
25+
const response = await fetch(ajaxUrl, {
26+
method: 'POST',
27+
credentials: 'same-origin',
28+
body: formData
29+
})
30+
31+
if ( !(response.status >= 200 && response.status < 300) ) {
32+
const error = {
33+
code: response.status,
34+
message: response.statusText
35+
}
36+
return { error }
37+
}
38+
39+
// Action response can be: { data } or { error }
40+
return await response.json()
41+
42+
} catch(error) {
43+
// Convert into error response
44+
return { error }
45+
}
46+
47+
}
48+
}

api/src/form.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
// Form
3+
4+
const inputFields = {
5+
input: true,
6+
select: true,
7+
textarea: true
8+
}
9+
10+
export function getFormData(formElement) {
11+
12+
// TODO: Use utility method from Form module
13+
14+
const data = {}
15+
16+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements
17+
const $inputs = formElement.elements
18+
19+
for (let index = 0; index < $inputs.length; index++) {
20+
21+
const $input = $inputs[index]
22+
const { nodeName, type, name } = $input
23+
24+
// Filter out fieldset, button, etc.
25+
if (!inputFields[ nodeName.toLowerCase() ]) continue
26+
27+
const value = type==='checkbox'
28+
? $input.checked // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox
29+
: $input.value
30+
31+
data[name] = value
32+
}
33+
34+
return data
35+
}

api/src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createAction } from './action'
2+
import { getFormData } from './form'
3+
4+
// From server side
5+
const api = window.Tangible.API
6+
const action = createAction(api)
7+
8+
Object.assign(api, {
9+
action,
10+
getFormData
11+
})

0 commit comments

Comments
 (0)