1 | <?php |
||
2 | /** |
||
3 | * Yii2 module for automatically generating XML Sitemap. |
||
4 | * |
||
5 | * @link https://github.com/himiklab/yii2-sitemap-module |
||
6 | * @author Serge Larin <[email protected]> |
||
7 | * @author HimikLab |
||
8 | * @copyright 2015 Assayer Pro Company |
||
9 | * @copyright Copyright (c) 2014 HimikLab |
||
10 | * @license http://opensource.org/licenses/MIT MIT |
||
11 | * |
||
12 | * based on https://github.com/himiklab/yii2-sitemap-module |
||
13 | */ |
||
14 | |||
15 | namespace assayerpro\sitemap; |
||
16 | |||
17 | use Yii; |
||
18 | use XMLWriter; |
||
19 | use yii\base\InvalidConfigException; |
||
20 | use yii\caching\Cache; |
||
21 | use yii\helpers\Url; |
||
22 | |||
23 | /** |
||
24 | * Yii2 module for automatically generating XML Sitemap. |
||
25 | * |
||
26 | * @author Serge Larin <[email protected]> |
||
27 | * @author HimikLab |
||
28 | * @package assayerpro\sitemap |
||
29 | */ |
||
30 | class Sitemap extends \yii\base\Component |
||
31 | { |
||
32 | const ALWAYS = 'always'; |
||
33 | const HOURLY = 'hourly'; |
||
34 | const DAILY = 'daily'; |
||
35 | const WEEKLY = 'weekly'; |
||
36 | const MONTHLY = 'monthly'; |
||
37 | const YEARLY = 'yearly'; |
||
38 | const NEVER = 'never'; |
||
39 | private $schemas = [ |
||
40 | 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9', |
||
41 | 'xmlns:image' => 'http://www.google.com/schemas/sitemap-image/1.1', |
||
42 | 'xmlns:news' => 'http://www.google.com/schemas/sitemap-news/0.9', |
||
43 | ]; |
||
44 | |||
45 | /** |
||
46 | * @var mixed renderedUrls |
||
47 | * @access private |
||
48 | */ |
||
49 | private $renderedUrls = []; |
||
50 | |||
51 | /** @var int Cache expiration time */ |
||
52 | public $cacheExpire = 86400; |
||
53 | |||
54 | /** @var string Cache key */ |
||
55 | public $cacheKey = 'sitemap'; |
||
56 | |||
57 | /** @var boolean Use php's gzip compressing. */ |
||
58 | public $enableGzip = false; |
||
59 | |||
60 | /** @var array Model list for sitemap */ |
||
61 | public $models = []; |
||
62 | |||
63 | /** @var array Url list for sitemap */ |
||
64 | public $urls = []; |
||
65 | |||
66 | /** @var int */ |
||
67 | public $maxSectionUrl = 20000; |
||
68 | |||
69 | /** @var bool Sort urls by priority. Top priority urls first */ |
||
70 | public $sortByPriority = false; |
||
71 | /** |
||
72 | * Build site map. |
||
73 | * @return array |
||
74 | */ |
||
75 | 5 | public function render() |
|
76 | { |
||
77 | 5 | $result = Yii::$app->cache->get($this->cacheKey); |
|
78 | 5 | if ($result) { |
|
79 | 1 | return $result; |
|
80 | } |
||
81 | 5 | $this->generateUrls(); |
|
82 | 5 | if ($this->sortByPriority) { |
|
83 | 1 | $this->sortUrlsByPriority(); |
|
84 | 1 | } |
|
85 | 5 | $parts = ceil(count($this->renderedUrls) / $this->maxSectionUrl); |
|
86 | 5 | if ($parts > 1) { |
|
87 | 1 | $xml = new XMLWriter(); |
|
88 | 1 | $xml->openMemory(); |
|
89 | 1 | $xml->startDocument('1.0', 'UTF-8'); |
|
90 | 1 | $xml->startElement('sitemapindex'); |
|
91 | 1 | $xml->writeAttribute('xmlns', $this->schemas['xmlns']); |
|
92 | 1 | for ($i = 1; $i <= $parts; $i++) { |
|
93 | 1 | $xml->startElement('sitemap'); |
|
94 | 1 | $xml->writeElement('loc', Url::to(['/sitemap/default/index', 'id' =>$i], true)); |
|
95 | 1 | $xml->writeElement('lastmod', static::dateToW3C(time())); |
|
96 | 1 | $xml->endElement(); |
|
97 | 1 | $result[$i]['file'] = Url::to(['/sitemap/default/index', 'id' =>$i], false); |
|
98 | 1 | } |
|
99 | 1 | $xml->endElement(); |
|
100 | 1 | $result[0]['xml'] = $xml->outputMemory(); |
|
101 | 1 | $result[0]['file'] = Url::to(['/sitemap/default/index']); |
|
102 | 1 | } |
|
103 | 5 | $urlItem = 0; |
|
104 | 5 | for ($i = 1; $i <= $parts; $i++) { |
|
105 | 5 | $xml = new XMLWriter(); |
|
106 | 5 | $xml->openMemory(); |
|
107 | 5 | $xml->startDocument('1.0', 'UTF-8'); |
|
108 | 5 | $xml->startElement('urlset'); |
|
109 | 5 | foreach ($this->schemas as $attr => $schemaUrl) { |
|
110 | 5 | $xml->writeAttribute($attr, $schemaUrl); |
|
111 | 5 | } |
|
112 | 5 | for (; ($urlItem < $i * $this->maxSectionUrl) && ($urlItem < count($this->renderedUrls)); $urlItem++) { |
|
113 | 5 | $xml->startElement('url'); |
|
114 | 5 | foreach ($this->renderedUrls[$urlItem] as $urlKey => $urlValue) { |
|
115 | 5 | if (is_array($urlValue)) { |
|
116 | switch ($urlKey) { |
||
117 | 1 | case 'news': |
|
118 | 1 | $namespace = 'news:'; |
|
119 | 1 | $xml->startElement($namespace.$urlKey); |
|
120 | 1 | static::hashToXML($urlValue, $xml, $namespace); |
|
121 | 1 | $xml->endElement(); |
|
122 | 1 | break; |
|
123 | 1 | case 'images': |
|
124 | 1 | $namespace = 'image:'; |
|
125 | 1 | foreach ($urlValue as $image) { |
|
126 | 1 | $xml->startElement($namespace.'image'); |
|
127 | 1 | static::hashToXML($image, $xml, $namespace); |
|
128 | 1 | $xml->endElement(); |
|
129 | 1 | } |
|
130 | 1 | break; |
|
131 | } |
||
132 | 1 | } else { |
|
133 | 5 | $xml->writeElement($urlKey, $urlValue); |
|
134 | } |
||
135 | 5 | } |
|
136 | 5 | $xml->endElement(); |
|
137 | 5 | } |
|
138 | |||
139 | 5 | $xml->endElement(); // urlset |
|
140 | 5 | $xml->endElement(); // document |
|
141 | 5 | $result[$i]['xml'] = $xml->outputMemory(); |
|
142 | 5 | } |
|
143 | |||
144 | 5 | if ($parts == 1) { |
|
145 | 4 | $result[0] = $result[1]; |
|
146 | 4 | unset($result[1]); |
|
147 | 4 | } |
|
148 | 5 | Yii::$app->cache->set($this->cacheKey, $result, $this->cacheExpire); |
|
149 | 5 | return $result; |
|
150 | } |
||
151 | |||
152 | /** |
||
153 | * Generate url's array from properties $url and $models |
||
154 | * |
||
155 | * @access protected |
||
156 | * @return array |
||
157 | */ |
||
158 | 5 | protected function generateUrls() |
|
159 | { |
||
160 | 5 | $this->renderedUrls = $this->urls; |
|
161 | |||
162 | 5 | foreach ($this->models as $modelName) { |
|
163 | /** @var behaviors\SitemapBehavior $model */ |
||
164 | 1 | if (is_array($modelName)) { |
|
165 | 1 | $model = new $modelName['class']; |
|
166 | 1 | if (isset($modelName['behaviors'])) { |
|
167 | 1 | $model->attachBehaviors($modelName['behaviors']); |
|
168 | 1 | } |
|
169 | 1 | } else { |
|
170 | 1 | $model = new $modelName; |
|
171 | } |
||
172 | 1 | $this->renderedUrls = array_merge($this->renderedUrls, $model->generateSiteMap()); |
|
173 | 5 | } |
|
174 | $this->renderedUrls = array_map(function($item) { |
||
175 | 5 | $item['loc'] = Url::to($item['loc'], true); |
|
176 | 5 | if (isset($item['lastmod'])) { |
|
177 | 1 | $item['lastmod'] = Sitemap::dateToW3C($item['lastmod']); |
|
178 | 1 | } |
|
179 | 5 | if (isset($item['images'])) { |
|
180 | $item['images'] = array_map(function($image) { |
||
181 | 1 | $image['loc'] = Url::to($image['loc'], true); |
|
182 | 1 | return $image; |
|
183 | 1 | }, $item['images']); |
|
184 | 1 | } |
|
185 | 5 | return $item; |
|
186 | 5 | }, $this->renderedUrls); |
|
187 | 5 | } |
|
188 | |||
189 | /** |
||
190 | * Convert associative arrays to XML |
||
191 | * |
||
192 | * @param array $hash |
||
193 | * @param XMLWriter $xml |
||
194 | * @param string $namespace |
||
195 | * @static |
||
196 | * @access protected |
||
197 | * @return XMLWriter |
||
198 | */ |
||
199 | 1 | protected static function hashToXML($hash, $xml, $namespace = '') |
|
200 | { |
||
201 | 1 | foreach ($hash as $key => $value) { |
|
202 | 1 | $xml->startElement($namespace.$key); |
|
203 | 1 | if (is_array($value)) { |
|
204 | 1 | static::hashToXML($value, $xml, $namespace); |
|
205 | 1 | } else { |
|
206 | 1 | $xml->text($value); |
|
207 | } |
||
208 | 1 | $xml->endElement(); |
|
209 | 1 | } |
|
210 | 1 | return $xml; |
|
211 | } |
||
212 | /** |
||
213 | * Convert date to W3C format |
||
214 | * |
||
215 | * @param mixed $date |
||
216 | * @static |
||
217 | * @access protected |
||
218 | * @return string |
||
219 | */ |
||
220 | 3 | public static function dateToW3C($date) |
|
221 | { |
||
222 | 3 | if (is_int($date)) { |
|
223 | 2 | return date(DATE_W3C, $date); |
|
224 | } else { |
||
225 | 2 | return date(DATE_W3C, strtotime($date)); |
|
226 | } |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * @return mixed |
||
231 | */ |
||
232 | protected function sortUrlsByPriority() |
||
233 | { |
||
234 | 1 | usort($this->renderedUrls, function ($urlA, $urlB) { |
|
235 | 1 | if (!isset($urlA['priority'])) { |
|
236 | return 1; |
||
237 | } |
||
238 | |||
239 | 1 | if (!isset($urlB['priority'])) { |
|
240 | 1 | return -1; |
|
241 | } |
||
242 | |||
243 | 1 | $a = $urlA['priority']; |
|
244 | 1 | $b = $urlB['priority']; |
|
245 | 1 | if ($a == $b) { |
|
246 | return 0; |
||
247 | } |
||
248 | |||
249 | 1 | return ($a < $b) ? 1 : -1; |
|
250 | 1 | }); |
|
251 | 1 | } |
|
252 | } |
||
253 |