Skip to content

Commit 19fed5a

Browse files
author
Mr Alexandre ELISÉ
committed
First commit
0 parents  commit 19fed5a

File tree

3 files changed

+324
-0
lines changed

3 files changed

+324
-0
lines changed

using-joomla-framework/.gitkeep

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* Add Joomla! articles from streamed csv url (with POST)
6+
*
7+
* @author Alexandre ELISÉ <[email protected]>
8+
* @copyright (c) 2009 - present. Alexandre ELISÉ. All rights reserved.
9+
* @license GPL-2.0-and-later GNU General Public License v2.0 or later
10+
* @link https://alexandree.io
11+
*/
12+
13+
// Public url of the sample csv used in this example (CHANGE WITH YOUR OWN CSV URL IF YOU WISH)
14+
$csvUrl = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTO8DC8xzEEyP754B0kBu1sa2P9Rn3I8OLmq_RJYHwOwTlY8OGvpjp1yvaE84Imj0HYQeJcNKT2TOFR/pub?gid=168068017&single=true&output=csv';
15+
16+
// HTTP Verb
17+
$httpVerb = 'POST';
18+
19+
// Your Joomla! 4.x website base url
20+
$baseUrl = 'https://example.org';
21+
$basePath = 'api/index.php/v1';
22+
23+
// This time we need endpoint to be a function to make it more dynamic
24+
$endpoint = function (string $givenBaseUrl, string $givenBasePath, int $givenResourceId = 0): string {
25+
return $givenResourceId ? sprintf('%s/%s/%s/%d', $givenBaseUrl, $givenBasePath, 'content/articles', $givenResourceId)
26+
: sprintf('%s/%s/%s', $givenBaseUrl, $givenBasePath, 'content/articles');
27+
};
28+
$timeout = 10;
29+
30+
// Add custom fields support (shout-out to Marc DECHÈVRE : CUSTOM KING)
31+
// The keys are the columns in the csv with the custom fields names (that's how Joomla! Web Services Api work as of today)
32+
// For the custom fields to work they need to be added in the csv and to exists in the Joomla! site.
33+
$customFieldKeys = []; //['with-coffee','with-dessert','extra-water-bottle'];
34+
35+
// Your Joomla! 4.x Api Token (DO NOT STORE IT IN YOUR REPO USE A VAULT OR A PASSWORD MANAGER)
36+
$token = '';
37+
38+
// PHP Generator to efficiently read the csv file
39+
$generator = function (string $url, array $keys = []): Generator {
40+
41+
if (empty($url))
42+
{
43+
yield new RuntimeException('Url MUST NOT be empty', 422);
44+
}
45+
46+
$defaultKeys = [
47+
'title',
48+
'alias',
49+
'catid',
50+
'articletext',
51+
'language',
52+
'metadesc',
53+
'metakey',
54+
'state',
55+
'featured',
56+
];
57+
58+
$mergedKeys = array_unique(array_merge($defaultKeys, $keys));
59+
60+
$resource = fopen($url, 'r');
61+
62+
if ($resource === false)
63+
{
64+
yield new RuntimeException('Could not read csv file', 500);
65+
}
66+
67+
try
68+
{
69+
//NON-BLOCKING I/O (Does not wait before processing next line.)
70+
stream_set_blocking($resource, false);
71+
72+
do
73+
{
74+
$currentLine = stream_get_line(
75+
$resource,
76+
0,
77+
"\r\n"
78+
);
79+
80+
if (empty($currentLine))
81+
{
82+
yield new RuntimeException('Current line MUST NOT be empty', 422);
83+
}
84+
85+
$extractedContent = str_getcsv($currentLine);
86+
87+
// Remove first element of csv line as it is usually the id of the article (since for POST it's not used, we remove it)
88+
array_shift($extractedContent);
89+
90+
if ($mergedKeys != $extractedContent)
91+
{
92+
$encodedContent = json_encode(array_combine($mergedKeys, $extractedContent));
93+
94+
yield $encodedContent;
95+
}
96+
yield new RuntimeException('Current line seem to be invalid', 422);
97+
} while (!feof($resource));
98+
} finally
99+
{
100+
fclose($resource);
101+
}
102+
};
103+
104+
// Read CSV in a PHP Generator using streams in non-blocking I/O mode
105+
$streamCsv = $generator($csvUrl, $customFieldKeys);
106+
107+
// Process data returned by the PHP Generator
108+
$process = function (string $givenHttpVerb, string $endpoint, string $dataString, array $headers, int $timeout, $transport) {
109+
curl_setopt_array($transport, [
110+
CURLOPT_URL => $endpoint,
111+
CURLOPT_RETURNTRANSFER => true,
112+
CURLOPT_ENCODING => 'utf-8',
113+
CURLOPT_MAXREDIRS => 10,
114+
CURLOPT_TIMEOUT => $timeout,
115+
CURLOPT_FOLLOWLOCATION => true,
116+
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2TLS,
117+
CURLOPT_CUSTOMREQUEST => $givenHttpVerb,
118+
CURLOPT_POSTFIELDS => $dataString,
119+
CURLOPT_HTTPHEADER => $headers,
120+
]
121+
);
122+
123+
$response = curl_exec($transport);
124+
// Might slow down the script but at least shows what's going on
125+
echo $response . PHP_EOL;
126+
return $response;
127+
};
128+
129+
foreach ($streamCsv as $dataString)
130+
{
131+
if (!is_string($dataString))
132+
{
133+
continue;
134+
}
135+
$curl = curl_init();
136+
try
137+
{
138+
// HTTP request headers
139+
$headers = [
140+
'Accept: application/vnd.api+json',
141+
'Content-Type: application/json',
142+
'Content-Length: ' . mb_strlen($dataString),
143+
sprintf('X-Joomla-Token: %s', trim($token)),
144+
];
145+
146+
$output = $process($httpVerb, $endpoint($baseUrl, $basePath, 0), $dataString, $headers, $timeout, $curl);
147+
// Continue even on partial failure
148+
if (empty($output) || array_key_exists('errors', json_decode($output, true)))
149+
{
150+
continue;
151+
}
152+
}
153+
catch (Throwable $e)
154+
{
155+
echo $e->getMessage() . PHP_EOL;
156+
continue;
157+
} finally
158+
{
159+
curl_close($curl);
160+
}
161+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* Edit Joomla! articles from streamed csv url (with PATCH)
6+
*
7+
* @author Alexandre ELISÉ <[email protected]>
8+
* @copyright (c) 2009 - present. Alexandre ELISÉ. All rights reserved.
9+
* @license GPL-2.0-and-later GNU General Public License v2.0 or later
10+
* @link https://alexandree.io
11+
*/
12+
13+
// Public url of the sample csv used in this example (CHANGE WITH YOUR OWN CSV URL IF YOU WISH)
14+
$csvUrl = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTO8DC8xzEEyP754B0kBu1sa2P9Rn3I8OLmq_RJYHwOwTlY8OGvpjp1yvaE84Imj0HYQeJcNKT2TOFR/pub?gid=168068017&single=true&output=csv';
15+
16+
// HTTP Verb
17+
$httpVerb = 'PATCH';
18+
19+
// Your Joomla! 4.x website base url
20+
$baseUrl = 'https://example.org';
21+
$basePath = 'api/index.php/v1';
22+
23+
// This time we need endpoint to be a function to make it more dynamic
24+
$endpoint = function (string $givenBaseUrl, string $givenBasePath, int $givenResourceId = 0): string {
25+
return $givenResourceId ? sprintf('%s/%s/%s/%d', $givenBaseUrl, $givenBasePath, 'content/articles', $givenResourceId)
26+
: sprintf('%s/%s/%s', $givenBaseUrl, $givenBasePath, 'content/articles');
27+
};
28+
29+
$timeout = 10;
30+
31+
// Add custom fields support (shout-out to Marc DECHÈVRE : CUSTOM KING)
32+
// The keys are the columns in the csv with the custom fields names (that's how Joomla! Web Services Api work as of today)
33+
// For the custom fields to work they need to be added in the csv and to exists in the Joomla! site.
34+
$customFieldKeys = []; //['with-coffee','with-dessert','extra-water-bottle'];
35+
36+
// Your Joomla! 4.x Api Token (DO NOT STORE IT IN YOUR REPO USE A VAULT OR A PASSWORD MANAGER)
37+
$token = '';
38+
39+
// PHP Generator to efficiently read the csv file
40+
$generator = function (string $url, array $keys = []): Generator {
41+
42+
if (empty($url))
43+
{
44+
yield new RuntimeException('Url MUST NOT be empty', 422);
45+
}
46+
47+
$defaultKeys = [
48+
'id',
49+
'title',
50+
'alias',
51+
'catid',
52+
'articletext', //does not work when using PATCH
53+
'introtext',
54+
'fulltext',
55+
'language',
56+
'metadesc',
57+
'metakey',
58+
'state',
59+
'featured',
60+
];
61+
62+
$mergedKeys = array_unique(array_merge($defaultKeys, $keys));
63+
64+
$resource = fopen($url, 'r');
65+
66+
if ($resource === false)
67+
{
68+
yield new RuntimeException('Could not read csv file', 500);
69+
}
70+
71+
try
72+
{
73+
//NON-BLOCKING I/O (Does not wait before processing next line.)
74+
stream_set_blocking($resource, false);
75+
76+
do
77+
{
78+
$currentLine = stream_get_line(
79+
$resource,
80+
0,
81+
"\r\n"
82+
);
83+
84+
if (empty($currentLine))
85+
{
86+
yield new RuntimeException('Current line MUST NOT be empty', 422);
87+
}
88+
89+
$extractedContent = str_getcsv($currentLine);
90+
91+
if ($mergedKeys != $extractedContent)
92+
{
93+
$encodedContent = json_encode(array_combine($mergedKeys, $extractedContent));
94+
95+
yield $encodedContent;
96+
}
97+
yield new RuntimeException('Current line seem to be invalid', 422);
98+
} while (!feof($resource));
99+
} finally
100+
{
101+
fclose($resource);
102+
}
103+
};
104+
105+
// Read CSV in a PHP Generator using streams in non-blocking I/O mode
106+
$streamCsv = $generator($csvUrl, $customFieldKeys);
107+
108+
// Process data returned by the PHP Generator
109+
$process = function (string $givenHttpVerb, string $endpoint, string $dataString, array $headers, int $timeout, $transport) {
110+
curl_setopt_array($transport, [
111+
CURLOPT_URL => $endpoint,
112+
CURLOPT_RETURNTRANSFER => true,
113+
CURLOPT_ENCODING => 'utf-8',
114+
CURLOPT_MAXREDIRS => 10,
115+
CURLOPT_TIMEOUT => $timeout,
116+
CURLOPT_FOLLOWLOCATION => true,
117+
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2TLS,
118+
CURLOPT_CUSTOMREQUEST => $givenHttpVerb,
119+
CURLOPT_POSTFIELDS => $dataString,
120+
CURLOPT_HTTPHEADER => $headers,
121+
]
122+
);
123+
124+
$response = curl_exec($transport);
125+
// Might slow down the script but at least shows what's going on
126+
echo $response . PHP_EOL;
127+
128+
return $response;
129+
};
130+
131+
foreach ($streamCsv as $dataString)
132+
{
133+
if (!is_string($dataString))
134+
{
135+
continue;
136+
}
137+
$curl = curl_init();
138+
try
139+
{
140+
// HTTP request headers
141+
$headers = [
142+
'Accept: application/vnd.api+json',
143+
'Content-Type: application/json',
144+
'Content-Length: ' . mb_strlen($dataString),
145+
sprintf('X-Joomla-Token: %s', trim($token)),
146+
];
147+
148+
$output = $process($httpVerb, $endpoint($baseUrl, $basePath, (int) (json_decode($dataString, true)['id'])), $dataString, $headers, $timeout, $curl);
149+
// Continue even on partial failure
150+
if (empty($output) || array_key_exists('errors', json_decode($output, true)))
151+
{
152+
continue;
153+
}
154+
}
155+
catch (Throwable $e)
156+
{
157+
echo $e->getMessage() . PHP_EOL;
158+
continue;
159+
} finally
160+
{
161+
curl_close($curl);
162+
}
163+
}

0 commit comments

Comments
 (0)