1+ <?php
2+
3+ namespace AIOSEO \Plugin \Common \Ai ;
4+
5+ // Exit if accessed directly.
6+ if ( ! defined ( 'ABSPATH ' ) ) {
7+ exit ;
8+ }
9+
10+ /**
11+ * AI class.
12+ *
13+ * @since 4.8.4
14+ */
15+ class Ai {
16+ /**
17+ * The base URL for the licensing server.
18+ *
19+ * @since 4.8.4
20+ *
21+ * @var string
22+ */
23+ private $ licensingUrl = 'https://licensing.aioseo.com/v1/ ' ;
24+
25+ /**
26+ * The action name for fetching credits.
27+ *
28+ * @since 4.8.4
29+ *
30+ * @var string
31+ */
32+ protected $ creditFetchAction = 'aioseo_ai_update_credits ' ;
33+
34+ /**
35+ * Class constructor.
36+ *
37+ * @since 4.8.4
38+ */
39+ public function __construct () {
40+ add_action ( 'init ' , [ $ this , 'getAccessToken ' ] );
41+
42+ add_action ( 'init ' , [ $ this , 'scheduleCreditFetchAction ' ] );
43+ add_action ( $ this ->creditFetchAction , [ $ this , 'updateCredits ' ] );
44+
45+ // If param is set, fetch credits but just once per 5 minutes to prevent abuse.
46+ if ( isset ( $ _REQUEST ['aioseo-ai-credits ' ] ) && ! aioseo ()->core ->cache ->get ( 'ai_get_credits ' ) ) { // phpcs:ignore HM.Security.NonceVerification.Recommended
47+ add_action ( 'init ' , [ $ this , 'updateCredits ' ] );
48+
49+ aioseo ()->core ->cache ->update ( 'ai_get_credits ' , true , 5 * MINUTE_IN_SECONDS );
50+ }
51+ }
52+
53+ /**
54+ * Gets an access token from the server.
55+ * This is the one-time access token that includes 50 free credits.
56+ *
57+ * @since 4.8.4
58+ *
59+ * @param bool $refresh Whether to refresh the access token.
60+ * @return void
61+ */
62+ public function getAccessToken ( $ refresh = false ) {
63+ // Check if user has an access token. If not, get one from the server.
64+ if ( aioseo ()->internalOptions ->internal ->ai ->accessToken && ! $ refresh ) {
65+ return ;
66+ }
67+
68+ if ( aioseo ()->cache ->get ( 'ai-access-token-error ' ) ) {
69+ return ;
70+ }
71+
72+ $ response = wp_remote_post ( $ this ->getApiUrl () . 'ai/auth/ ' , [
73+ 'body ' => [
74+ 'domain ' => aioseo ()->helpers ->getSiteDomain ()
75+ ]
76+ ] );
77+
78+ if ( is_wp_error ( $ response ) ) {
79+ aioseo ()->cache ->update ( 'ai-access-token-error ' , true , 1 * HOUR_IN_SECONDS );
80+
81+ // Schedule another, one-time event in approx. 1 hour from now.
82+ aioseo ()->actionScheduler ->scheduleSingle ( $ this ->creditFetchAction , 1 * ( HOUR_IN_SECONDS + wp_rand ( 0 , 30 * MINUTE_IN_SECONDS ) ), [] );
83+
84+ return ;
85+ }
86+
87+ $ body = wp_remote_retrieve_body ( $ response );
88+ $ data = json_decode ( $ body );
89+ if ( empty ( $ data ->accessToken ) ) {
90+ aioseo ()->cache ->update ( 'ai-access-token-error ' , true , 1 * HOUR_IN_SECONDS );
91+
92+ // Schedule another, one-time event in approx. 1 hour from now.
93+ aioseo ()->actionScheduler ->scheduleSingle ( $ this ->creditFetchAction , 1 * ( HOUR_IN_SECONDS + wp_rand ( 0 , 30 * MINUTE_IN_SECONDS ) ), [] );
94+
95+ return ;
96+ }
97+
98+ aioseo ()->internalOptions ->internal ->ai ->accessToken = sanitize_text_field ( $ data ->accessToken );
99+ aioseo ()->internalOptions ->internal ->ai ->isTrialAccessToken = $ data ->isFree ?? false ;
100+
101+ // Fetch the credit totals.
102+ $ this ->updateCredits ( true );
103+ }
104+
105+ /**
106+ * Schedules the credit fetch action.
107+ *
108+ * @since 4.8.4
109+ *
110+ * @return void
111+ */
112+ public function scheduleCreditFetchAction () {
113+ // If not set up, create a scheduled action to refresh the credits each day.
114+ if ( ! aioseo ()->actionScheduler ->isScheduled ( $ this ->creditFetchAction ) ) {
115+ aioseo ()->actionScheduler ->scheduleRecurrent ( $ this ->creditFetchAction , DAY_IN_SECONDS , DAY_IN_SECONDS , [] );
116+ }
117+ }
118+
119+ /**
120+ * Gets the credit data from the server and updates our options.
121+ *
122+ * @since 4.8.4
123+ *
124+ * @param bool $refresh Whether to refresh the credits forcefully.
125+ * @return void
126+ */
127+ public function updateCredits ( $ refresh = false ) {
128+ if ( aioseo ()->cache ->get ( 'ai-credits-error ' ) && ! $ refresh ) {
129+ return ;
130+ }
131+
132+ if ( ! aioseo ()->internalOptions ->internal ->ai ->accessToken ) {
133+ return ;
134+ }
135+
136+ $ response = aioseo ()->helpers ->wpRemoteGet ( $ this ->getApiUrl () . 'ai/credits/ ' , [
137+ 'headers ' => $ this ->getRequestHeaders ()
138+ ] );
139+
140+ if ( is_wp_error ( $ response ) ) {
141+ aioseo ()->cache ->update ( 'ai-credits-error ' , true , HOUR_IN_SECONDS );
142+
143+ // Schedule another, one-time event in approx. 1 hour from now.
144+ aioseo ()->actionScheduler ->scheduleSingle ( $ this ->creditFetchAction , 1 * ( HOUR_IN_SECONDS + wp_rand ( 0 , 30 * MINUTE_IN_SECONDS ) ), [] );
145+
146+ return ;
147+ }
148+
149+ $ body = wp_remote_retrieve_body ( $ response );
150+ $ data = json_decode ( $ body );
151+ if ( empty ( $ data ->success ) ) {
152+ aioseo ()->cache ->update ( 'ai-credits-error ' , true , HOUR_IN_SECONDS );
153+
154+ // Schedule another, one-time event in approx. 1 hour from now.
155+ aioseo ()->actionScheduler ->scheduleSingle ( $ this ->creditFetchAction , 1 * ( HOUR_IN_SECONDS + wp_rand ( 0 , 30 * MINUTE_IN_SECONDS ) ), [] );
156+
157+ return ;
158+ }
159+
160+ $ orders = [];
161+ if ( ! empty ( $ data ->orders ) ) {
162+ foreach ( $ data ->orders as $ order ) {
163+ if (
164+ empty ( $ order ->total ) ||
165+ ! isset ( $ order ->remaining ) ||
166+ ! isset ( $ order ->expires )
167+ ) {
168+ continue ;
169+ }
170+
171+ $ orders [] = [
172+ 'total ' => intval ( $ order ->total ),
173+ 'remaining ' => intval ( $ order ->remaining ),
174+ 'expires ' => intval ( $ order ->expires )
175+ ];
176+ }
177+ }
178+
179+ aioseo ()->internalOptions ->internal ->ai ->credits ->orders = $ orders ;
180+ aioseo ()->internalOptions ->internal ->ai ->credits ->total = isset ( $ data ->total ) ? intval ( $ data ->total ) : 0 ;
181+ aioseo ()->internalOptions ->internal ->ai ->credits ->remaining = isset ( $ data ->remaining ) ? intval ( $ data ->remaining ) : 0 ;
182+
183+ if ( ! empty ( $ data ->license ) ) {
184+ aioseo ()->internalOptions ->internal ->ai ->credits ->license ->total = intval ( $ data ->license ->total );
185+ aioseo ()->internalOptions ->internal ->ai ->credits ->license ->remaining = intval ( $ data ->license ->remaining );
186+ aioseo ()->internalOptions ->internal ->ai ->credits ->license ->expires = intval ( $ data ->license ->expires );
187+ } else {
188+ aioseo ()->internalOptions ->internal ->ai ->credits ->license ->reset ();
189+ }
190+ }
191+
192+ /**
193+ * Returns the default request headers.
194+ *
195+ * @since 4.8.4
196+ *
197+ * @return array The default request headers.
198+ */
199+ protected function getRequestHeaders () {
200+ $ headers = [
201+ 'X-AIOSEO-Ai-Token ' => aioseo ()->internalOptions ->internal ->ai ->accessToken ,
202+ 'X-AIOSEO-Ai-Domain ' => aioseo ()->helpers ->getSiteDomain ()
203+ ];
204+
205+ return $ headers ;
206+ }
207+
208+ /**
209+ * Returns the API URL of the licensing server.
210+ *
211+ * @since 4.8.4
212+ *
213+ * @return string The URL.
214+ */
215+ protected function getApiUrl () {
216+ if ( defined ( 'AIOSEO_LICENSING_URL ' ) ) {
217+ return AIOSEO_LICENSING_URL ;
218+ }
219+
220+ return $ this ->licensingUrl ;
221+ }
222+ }
0 commit comments