precacher.php
8.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
<?php
// namespace components\com_jmap\libraries\xml;
/**
*
* @package JMAP::FRAMEWORK::components::com_jmap
* @subpackage framework
* @subpackage xml
* @author Joomla! Extensions Store
* @copyright (C) 2015 - Joomla! Extensions Store
* @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
*/
defined ( '_JEXEC' ) or die ( 'Restricted access' );
/**
* Sitemaps XML precacher public responsibilities
*
* @package JMAP::FRAMEWORK::components::com_jmap
* @subpackage framework
* @subpackage xml
* @since 2.3
*/
interface IJMapXmlPrecacher {
/**
* Merge the sitemap generated in the current iteration in the
* precaching file, temp file during iterations process, renamed at the end
*
* @access public
* @param string $sitemapIterationData
* The formatted XML string for the current sitemap iteration generation
* @return Object
*/
public function mergeSitemap($sitemapIterationData = null);
}
/**
* Precacher for XML sitemaps
* This class is responsible to manage correct merge for every generation iteration,
* it has to strip out start, both, end tags urlset based on process status and
* finally write precaching file to disk renaming it only if process is finished
* and last ajax request is detected
*
* <<testable_behavior>>
*
* @package JMAP::FRAMEWORK::components::com_jmap
* @subpackage libraries
* @subpackage xml
* @since 2.3
*/
class JMapXmlPrecacher implements IJMapXmlPrecacher {
/**
* Path to store precaching files
*
* @access private
* @var string
*/
private $preCachingPath;
/**
* File name based on the hash of posted params and sietmap type/format
*
* @access private
* @var string
*/
private $fileName;
/**
* Once finished processing and after renaming and finalizing file try
* to get and send to JS client the filemtime to show in the green label
*
* @access private
* @var string
*/
private $finalFileMTime;
/**
* Object to perform file writing tasks in write append mode
* Support stream context
*
* @access protected
* @var string
*/
protected $fileStreamWriter;
/**
* True if the ajax request is the first in the whole process
* If = 'start' the start tag is not stripped out and the close tag is stripped out
* If = 'run' both tags are stripped out we are in the middle of the processing
* If = 'end' the close tag is not stripped out and the start tag is stripped out
*
* @access protected
* @var string
*/
protected $processStatus;
/**
* Application reference
*
* @access protected
* @var Object
*/
protected $app;
/**
* Strip the start/end tags based on process status
*
* @access protected
* @param string $xmlData
* @return string
*/
protected function stripSitemapTags($xmlData) {
// Data are missing
if (! $xmlData && $this->processStatus != 'end') {
return null;
}
// Evaluate process status and strip iteration tags accordingly
switch ($this->processStatus) {
case 'start' :
$xmlData = preg_replace ( "/<\/urlset(.|\s)*?>/i", '', $xmlData );
$xmlData = rtrim ( $xmlData, PHP_EOL );
break;
case 'end' :
$xmlData = '</urlset>';
break;
case 'run' :
default :
$xmlData = preg_replace ( "/<\/?\?xml(.|\s)*?>/i", null, $xmlData );
$xmlData = preg_replace ( "/<urlset(.|\s)*?>/i", null, $xmlData );
$xmlData = preg_replace ( "/<\/urlset(.|\s)*?>/i", null, $xmlData );
$xmlData = trim ( $xmlData, PHP_EOL );
break;
}
return $xmlData;
}
/**
* Write precaching file on disk accordingly to request params hash
* If processStatus is detected as end, the temp named file is renamed to
* final name that will be used by main display controller as precached sitemap
*
* @access protected
* @return boolean
*/
protected function writeFile($data) {
// Never write if no data
if(!$data) {
return false;
}
// Manage file name as temp, to avoid that not complete operations have resulting broken sitemap files
$tempFileName = $this->preCachingPath . 'temp_' . $this->fileName;
// Delete any pre-existant incomplete temp processing files - otherwise the result would be append again
if($this->processStatus == 'start' && file_exists($tempFileName)) {
if(!JFile::delete($tempFileName)) {
throw new JMapExceptionPrecaching ( JText::_ ( 'COM_JMAP_PRECACHING_ERROR_DELETE_TEMPFILE'), 'error', 'delete_temp_file' );
}
}
// Open file in write append mode
if (! $this->fileStreamWriter->open ( $tempFileName, 'a' )) {
throw new JMapExceptionPrecaching ( JText::sprintf ( 'COM_JMAP_PRECACHING_ERROR_OPENING_FILE', $this->fileStreamWriter->getError () ), 'error', 'open_file' );
}
// Write append to file
if ($data) {
// Add always new line if process status is not start
if ($this->processStatus != 'start') {
$data = PHP_EOL . $data;
}
// Try to append data to precaching file
$result = $this->fileStreamWriter->write ( $data );
// Something went wrong
if (! $result) {
throw new JMapExceptionPrecaching ( JText::sprintf ( 'COM_JMAP_PRECACHING_ERROR_WRITING_FILE', $this->fileStreamWriter->getError () ), 'error', 'write_file' );
}
}
// Finished operation, close file handle
if (! $this->fileStreamWriter->close ()) {
throw new JMapExceptionPrecaching ( JText::sprintf ( 'COM_JMAP_PRECACHING_ERROR_CLOSING_FILE', $this->fileStreamWriter->getError () ), 'error', 'close_file' );
}
// Check if process status has ended successfully, and if so try to rename temp file to be ready to use
if ($this->processStatus == 'end') {
if (! rename ( $tempFileName, $this->preCachingPath . $this->fileName )) {
throw new JMapExceptionPrecaching ( JText::_ ( 'COM_JMAP_PRECACHING_ERROR_RENAMING_FILE' ), 'error', 'rename_file' );
}
// Set filemtime
$joomlaConfig = JFactory::getConfig();
$localTimeZone = new DateTimeZone($joomlaConfig->get('offset'));
$lastGenerationTimestamp = filemtime ( $this->preCachingPath . $this->fileName );
$dateObject = new JDate($lastGenerationTimestamp);
$dateObject->setTimezone($localTimeZone);
$this->finalFileMTime = $dateObject->format('Y-m-d', true);
}
return true;
}
/**
* Merge the sitemap generated in the current iteration in the
* precaching file, temp file during iterations process, renamed at the end
*
* @access public
* @param string $sitemapIterationData
* The formatted XML string for the current sitemap iteration generation, if the injected data is null it means
* that we are on the last end iteration so no need to grab data from model but only append the </urlset>
* @return Object
*/
public function mergeSitemap($sitemapIterationData = null) {
// Response JSON object
$response = new stdClass ();
try {
// Strip tags according to process status
$strippedData = $this->stripSitemapTags ( $sitemapIterationData );
// Write sitemap iteration data to file
$this->writeFile ( $strippedData, $this->fileName );
// If process ended and filemtime is set, return to js app to show inside label
if($this->finalFileMTime) {
$response->lastgeneration = $this->finalFileMTime;
}
} catch ( JMapExceptionPrecaching $e ) {
$response->result = false;
$response->exception_message = $e->getMessage ();
$response->context = $e->getContext ();
return $response;
} catch ( Exception $e ) {
$jmapException = new JMapExceptionPrecaching ( $e->getMessage (), 'error', 'joomla_framework' );
$response->result = false;
$response->exception_message = $jmapException->getMessage ();
$response->context = $jmapException->getContext ();
return $response;
}
// Manage exceptions from DB Model and return to JS domain
$response->result = true;
return $response;
}
/**
* Class constructor
*
* @access public
* @param string $fileName
* @param Object $fileWriter
* @return Object&
*/
public function __construct($fileName, JStream $fileWriter) {
$this->app = JFactory::getApplication ();
// Set precaching path
$this->preCachingPath = JPATH_COMPONENT_SITE . '/precache/';
// Set precaching filename based on sitemap params
$this->fileName = $fileName;
// Object to perform file storing tasks
$this->fileStreamWriter = $fileWriter;
// Set process status based on JS App Client
$this->processStatus = $this->app->input->get ( 'process_status' );
// Set always to null, value is available only after end of process
$this->finalFileMTime = null;
}
}