PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.2.13
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.2.13
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
table-builder-block / includes / Admin / Api / ChangelogData.php

ChangelogData.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables 2.2.13, at includes/Admin/Api/ChangelogData.php

171 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
171