PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.2.14
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.2.14
2.2.14 2.2.13 2.2.12 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 trunk 1.0.0 1.0.1 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2
← All changes | includes/Admin/Api/ChangelogData.php +170 -108 2.2.32.2.14 View file →
@@ -1,108 +1,170 @@
1 -<?php
2 -
3 -namespace TableBuilder\Admin\Api;
4 -
5 -defined('ABSPATH') || exit;
6 -
7 -class ChangelogData {
8 -
9 - private const ROUTE = 'changelog';
10 -
11 - private const SOURCES = [
12 - 'Free' => [
13 - 'file' => TABLE_BUILDER_BLOCK_PLUGIN_DIR . 'readme.txt',
14 - 'regex' => '/= Table Builder Block:\s*([^(=]+?)\s*\(([^)]+)\)\s*=\R(.*?)(?=\R= Table Builder Block:|\z)/s',
15 - ],
16 - ];
17 -
18 - public function __construct() {
19 - add_action('rest_api_init', [$this, 'register_routes']);
20 - }
21 -
22 - public function register_routes(): void {
23 - register_rest_route('tablebuilder/v1', self::ROUTE, [
24 - 'methods' => \WP_REST_Server::READABLE,
25 - 'callback' => [$this, 'get_changelog'],
26 - 'permission_callback' => fn() => current_user_can('manage_options'),
27 - ]);
28 - }
29 -
30 - public function get_changelog(\WP_REST_Request $request): \WP_REST_Response {
31 - if (!wp_verify_nonce($request->get_header('X-WP-Nonce'), 'wp_rest')) {
32 - return new \WP_REST_Response([], 403);
33 - }
34 -
35 - $items = array_merge(
36 - $this->parse_source('Free', self::SOURCES['Free']),
37 - $this->parse_source('Pro', $this->get_pro_source()),
38 - );
39 -
40 - usort($items, fn($a, $b) => strtotime($b['publish_date']) <=> strtotime($a['publish_date']));
41 -
42 - return new \WP_REST_Response($items, 200);
43 - }
44 -
45 - private function get_pro_source(): array {
46 - $dir = defined('TABLE_BUILDER_BLOCK_PRO_PLUGIN_DIR')
47 - ? TABLE_BUILDER_BLOCK_PRO_PLUGIN_DIR
48 - : dirname(rtrim(TABLE_BUILDER_BLOCK_PLUGIN_DIR, '/\\')) . '/table-builder-block-pro/';
49 -
50 - return [
51 - 'file' => $dir . 'changelog.txt',
52 - 'regex' => '/= Version:\s*([^(=]+?)\s*\(([^)]+)\)\s*=\R(.*?)(?=\R= Version:|\z)/s',
53 - ];
54 - }
55 -
56 - private function parse_source(string $type, array $source): array {
57 - if (!file_exists($source['file'])) {
58 - return [];
59 - }
60 -
61 - $content = file_get_contents($source['file']);
62 -
63 - if (!$content || !preg_match('/== Changelog ==\R(.*)$/s', $content, $m)) {
64 - return [];
65 - }
66 -
67 - preg_match_all($source['regex'], trim($m[1]), $entries, PREG_SET_ORDER);
68 -
69 - return array_filter(array_map(
70 - fn($entry) => $this->build_item($entry, $type),
71 - $entries
72 - ));
73 - }
74 -
75 - private function build_item(array $entry, string $type): ?array {
76 - $list_items = array_filter(
77 - array_map(fn($line) => $this->parse_list_line($line), preg_split('/\R/', trim($entry[3])))
78 - );
79 -
80 - if (empty($list_items)) {
81 - return null;
82 - }
83 -
84 - return [
85 - 'title' => trim($entry[1]),
86 - 'type' => $type,
87 - 'publish_date' => $this->format_date(trim($entry[2])),
88 - 'content' => '<ul>' . implode('', $list_items) . '</ul>',
89 - ];
90 - }
91 -
92 - private function parse_list_line(string $line): string {
93 - $line = trim($line);
94 -
95 - if (!str_starts_with($line, '*')) {
96 - return '';
97 - }
98 -
99 - $text = trim(ltrim($line, '* '));
100 -
101 - return $text !== '' ? '<li>' . esc_html($text) . '</li>' : '';
102 - }
103 -
104 - private function format_date(string $date): string {
105 - $timestamp = strtotime($date);
106 - return $timestamp ? gmdate('F j, Y', $timestamp) : $date;
107 - }
108 -}
1 +<?php
2 +/**
3 + * REST endpoint that parses and serves the Free + Pro changelog
4 + *
5 + * @package TableKit
6 + */
7 +
8 +namespace TableBuilder\Admin\Api;
9 +
10 +defined( 'ABSPATH' ) || exit;
11 +
12 +/**
13 + * Registers and serves the tablebuilder/v1/changelog REST route.
14 + */
15 +class ChangelogData {
16 +
17 + private const ROUTE = 'changelog';
18 +
19 + private const SOURCES = array(
20 + 'Free' => array(
21 + 'file' => TABLE_BUILDER_BLOCK_PLUGIN_DIR . 'readme.txt',
22 + 'regex' => '/= (?:TableKit|Table Builder Block)\s*:?\s*([^(=]+?)\s*\(([^)]+)\)\s*=\R(.*?)(?=\R= (?:TableKit|Table Builder Block)\s*:?\s*|\z)/s',
23 + ),
24 + );
25 +
26 + /**
27 + * Hooks changelog REST route registration into WordPress.
28 + */
29 + public function __construct() {
30 + add_action( 'rest_api_init', array( $this, 'register_routes' ) );
31 + }
32 +
33 + /**
34 + * Registers the tablebuilder/v1/changelog REST route.
35 + *
36 + * @return void
37 + */
38 + public function register_routes(): void {
39 + register_rest_route(
40 + 'tablebuilder/v1',
41 + self::ROUTE,
42 + array(
43 + 'methods' => \WP_REST_Server::READABLE,
44 + 'callback' => array( $this, 'get_changelog' ),
45 + 'permission_callback' => fn() => current_user_can( 'manage_options' ),
46 + )
47 + );
48 + }
49 +
50 + /**
51 + * REST callback: parse and return the combined Free + Pro changelog entries,
52 + * newest first.
53 + *
54 + * @param \WP_REST_Request $request Request; only its nonce header is used.
55 + * @return \WP_REST_Response
56 + */
57 + public function get_changelog( \WP_REST_Request $request ): \WP_REST_Response {
58 + if ( ! wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) {
59 + return new \WP_REST_Response( array(), 403 );
60 + }
61 +
62 + $items = array_merge(
63 + $this->parse_source( 'Free', self::SOURCES['Free'] ),
64 + $this->parse_source( 'Pro', $this->get_pro_source() ),
65 + );
66 +
67 + usort( $items, fn( $a, $b ) => strtotime( $b['publish_date'] ) <=> strtotime( $a['publish_date'] ) );
68 +
69 + return new \WP_REST_Response( $items, 200 );
70 + }
71 +
72 + /**
73 + * Builds the file/regex source descriptor for the Pro add-on's changelog.txt.
74 + *
75 + * @return array{file:string,regex:string}
76 + */
77 + private function get_pro_source(): array {
78 + $dir = defined( 'TABLE_BUILDER_BLOCK_PRO_PLUGIN_DIR' )
79 + ? TABLE_BUILDER_BLOCK_PRO_PLUGIN_DIR
80 + : dirname( rtrim( TABLE_BUILDER_BLOCK_PLUGIN_DIR, '/\\' ) ) . '/table-builder-block-pro/';
81 +
82 + return array(
83 + 'file' => $dir . 'changelog.txt',
84 + 'regex' => '/= Version:\s*([^(=]+?)\s*\(([^)]+)\)\s*=\R(.*?)(?=\R= Version:|\z)/s',
85 + );
86 + }
87 +
88 + /**
89 + * Parses a "== Changelog ==" section out of a source file into changelog items.
90 + *
91 + * @param string $type Source label, e.g. "Free" or "Pro".
92 + * @param array $source Source descriptor with "file" and "regex" keys.
93 + * @return array Parsed changelog items (see build_item()), possibly empty.
94 + */
95 + private function parse_source( string $type, array $source ): array {
96 + if ( ! file_exists( $source['file'] ) ) {
97 + return array();
98 + }
99 +
100 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reads a local file bundled with the plugin (readme.txt / Pro changelog.txt), not a remote URL; wp_remote_get() doesn't apply here.
101 + $content = file_get_contents( $source['file'] );
102 +
103 + if ( ! $content || ! preg_match( '/== Changelog ==\R(.*)$/s', $content, $m ) ) {
104 + return array();
105 + }
106 +
107 + preg_match_all( $source['regex'], trim( $m[1] ), $entries, PREG_SET_ORDER );
108 +
109 + return array_filter(
110 + array_map(
111 + fn( $entry ) => $this->build_item( $entry, $type ),
112 + $entries
113 + )
114 + );
115 + }
116 +
117 + /**
118 + * Builds a single changelog item from a regex-matched version-entry block.
119 + *
120 + * @param array $entry Regex match groups: [1]=title, [2]=date, [3]=body lines.
121 + * @param string $type Source label, e.g. "Free" or "Pro".
122 + * @return array{title:string,type:string,publish_date:string,content:string}|null
123 + * The parsed item, or null if it has no bullet-point content.
124 + */
125 + private function build_item( array $entry, string $type ): ?array {
126 + $list_items = array_filter(
127 + array_map( fn( $line ) => $this->parse_list_line( $line ), preg_split( '/\R/', trim( $entry[3] ) ) )
128 + );
129 +
130 + if ( empty( $list_items ) ) {
131 + return null;
132 + }
133 +
134 + return array(
135 + 'title' => trim( $entry[1] ),
136 + 'type' => $type,
137 + 'publish_date' => $this->format_date( trim( $entry[2] ) ),
138 + 'content' => '<ul>' . implode( '', $list_items ) . '</ul>',
139 + );
140 + }
141 +
142 + /**
143 + * Converts one "* ..." changelog bullet line into a list item element.
144 + *
145 + * @param string $line Raw line from the changelog body.
146 + * @return string The <li> markup, or an empty string if the line isn't a bullet.
147 + */
148 + private function parse_list_line( string $line ): string {
149 + $line = trim( $line );
150 +
151 + if ( ! str_starts_with( $line, '*' ) ) {
152 + return '';
153 + }
154 +
155 + $text = trim( ltrim( $line, '* ' ) );
156 +
157 + return '' !== $text ? '<li>' . esc_html( $text ) . '</li>' : '';
158 + }
159 +
160 + /**
161 + * Format a changelog date string into a human-readable "Month D, Year" form.
162 + *
163 + * @param string $date Raw date string as it appears in the changelog.
164 + * @return string Formatted date, or the original string if it couldn't be parsed.
165 + */
166 + private function format_date( string $date ): string {
167 + $timestamp = strtotime( $date );
168 + return $timestamp ? gmdate( 'F j, Y', $timestamp ) : $date;
169 + }
170 +}