-
Notifications
You must be signed in to change notification settings - Fork 9
/
FieldtypeSeoMaestro.module.php
398 lines (354 loc) · 15.4 KB
/
FieldtypeSeoMaestro.module.php
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
<?php
namespace ProcessWire;
use SeoMaestro\FormManager;
use SeoMaestro\PageFieldValue;
/**
* A fieldtype storing various metatags and sitemap settings for a page.
*/
class FieldtypeSeoMaestro extends Fieldtype implements Module
{
public static function getModuleInfo()
{
return [
'title' => 'Seo Maestro',
'summary' => 'A fieldtype storing various meta tags (meta, opengraph, twitter etc.) and sitemap behaviour for pages.',
'version' => '1.1.1',
'author' => 'Stefan Wanzenried (Wanze)',
'installs' => 'InputfieldSeoMaestro',
'requires' => [
'PHP>=7.0.0',
'ProcessWire>=3.0.0',
'SeoMaestro',
],
];
}
/**
* {@inheritdoc}
*/
public function sanitizeValue(Page $page, Field $field, $value)
{
if ($value instanceof PageFieldValue) {
return $value;
}
return $this->getBlankValue($page, $field);
}
/**
* {@inheritdoc}
*/
public function getBlankValue(Page $page, Field $field)
{
return $this->wire(new PageFieldValue($page, $field, $this->seoMaestro(), []));
}
/**
* {@inheritdoc}
*/
public function ___wakeupValue(Page $page, Field $field, $value)
{
$data = $value['data'] ? json_decode($value['data'], true) : [];
return $this->wire(new PageFieldValue($page, $field, $this->seoMaestro(), $data));
}
/**
* {@inheritdoc}
*/
public function ___sleepValue(Page $page, Field $field, $value)
{
return [
'data' => json_encode($value->getArray()),
'meta_inherit' => $this->doesGroupInheritData('meta', $value) ? 1 : 0,
'opengraph_inherit' => $this->doesGroupInheritData('opengraph', $value) ? 1 : 0,
'twitter_inherit' => $this->doesGroupInheritData('twitter', $value) ? 1 : 0,
'robots_inherit' => $this->doesGroupInheritData('robots', $value) ? 1 : 0,
'structuredData_inherit' => $this->doesGroupInheritData('structuredData', $value) ? 1 : 0,
'sitemap_inherit' => $this->doesGroupInheritData('sitemap', $value) ? 1 : 0,
'sitemap_include' => $value->get('sitemap')->include ? 1 : 0,
];
}
/**
* {@inheritdoc}
*/
public function getInputfield(Page $page, Field $field)
{
$inputfield = $this->wire('modules')->get('InputfieldSeoMaestro');
$inputfield->setField($field);
return $inputfield;
}
/**
* {@inheritdoc}
*/
public function getDatabaseSchema(Field $field)
{
$schema = parent::getDatabaseSchema($field);
$schema['data'] = 'text NOT NULL';
// Add flags to quickly lookup information with selectors.
// The "inherit" flags are true, if a page inherits all data of a group from the SeoMaestro field.
$schema['meta_inherit'] = 'tinyint UNSIGNED NOT NULL';
$schema['opengraph_inherit'] = 'tinyint UNSIGNED NOT NULL';
$schema['twitter_inherit'] = 'tinyint UNSIGNED NOT NULL';
$schema['robots_inherit'] = 'tinyint UNSIGNED NOT NULL';
$schema['sitemap_inherit'] = 'tinyint UNSIGNED NOT NULL';
$schema['structuredData_inherit'] = 'tinyint UNSIGNED NOT NULL';
// Is the page included in the sitemap?
$schema['sitemap_include'] = 'tinyint UNSIGNED NOT NULL';
// Remove the index on the data column.
unset($schema['keys']['data']);
return $schema;
}
/**
* {@inheritdoc}
*/
public function ___getCompatibleFieldtypes(Field $field)
{
return null;
}
/**
* @return array
*/
public function getSeoData()
{
return [
'meta_title' => [
'label' => $this->_('Title'),
'description' => $this->_('Every page should have a unique Meta Title, ideally less than 60 characters long.'),
'translatable' => true,
],
'meta_description' => [
'label' => $this->_('Description'),
'description' => $this->_('Every page should have a unique Meta Description, ideally less than 160 characters long.'),
'translatable' => true,
],
'meta_keywords' => [
'label' => $this->_('Keywords'),
'description' => $this->_('A comma-separated list of keywords about the page. This meta tag is *no longer* supported by most search engines.'),
'translatable' => true,
],
'meta_canonicalUrl' => [
'label' => $this->_('Canonical URL'),
'description' => $this->_('The URL of this page that a search engine thinks is most representative from a set of duplicate pages.'),
'translatable' => true,
],
'opengraph_title' => [
'label' => $this->_('Title'),
'translatable' => true,
],
'opengraph_description' => [
'label' => $this->_('Description'),
'translatable' => true,
],
'opengraph_image' => [
'label' => $this->_('Image'),
'description' => $this->_('This image is used as a social network preview image. Enter an absolute URL to a image or a placeholder for an image field, e.g. `{image}`. If the placeholder references an image field holding multiple images, the first one is used.'),
],
'opengraph_imageAlt' => [
'label' => $this->_('Image Alt'),
'description' => $this->_('A description of what is in the image (not a caption). If the page specifies an `og:image` it should specify `og:image:alt`.'),
'translatable' => true,
],
'opengraph_type' => [
'label' => $this->_('Type'),
'description' => $this->_('The type of your object. Depending on the type you specify, other properties may also be required.'),
],
'opengraph_locale' => [
'label' => $this->_('Locale'),
'description' => $this->_('The locale these tags are marked up in. Of the format `language_TERRITORY`.'),
'translatable' => true,
],
'opengraph_siteName' => [
'label' => $this->_('Site Name'),
'description' => $this->_('The name which should be displayed for the overall site.'),
],
'twitter_card' => [
'label' => $this->_('Card'),
'description' => $this->_('The card type, which will be one of `summary`, `summary_large_image`, `app`, or `player`.'),
],
'twitter_site' => [
'label' => $this->_('Site'),
'description' => $this->_('@username for the website used in the card footer.'),
],
'twitter_creator' => [
'label' => $this->_('Creator'),
'description' => $this->_('@username for the content creator/author.'),
],
'robots_noIndex' => [
'label' => $this->_('Prevent search engines from indexing this page'),
],
'robots_noFollow' => [
'label' => $this->_('Prevent search engines from following links on this page'),
],
'structuredData_breadcrumb' => [
'label' => $this->_('Breadcrumb'),
'description' => $this->_('Google Search uses breadcrumb markup to categorize the information from the page in search results.'),
'notes' => $this->_('See: https://developers.google.com/search/docs/data-types/breadcrumb'),
],
'sitemap_include' => [
'label' => $this->_('Include in Sitemap'),
],
'sitemap_priority' => [
'label' => $this->_('Priority'),
'description' => $this->_('The priority of this URL relative to other URLs on your site. Valid values range from `0.0` to `1.0`.'),
],
'sitemap_changeFrequency' => [
'label' => $this->_('Change Frequency'),
'description' => $this->_('A hint to search engines on how frequently the page is likely to change.'),
],
];
}
/**
* Return the Seo data indexed by group.
*
* @return array
*/
public function getSeoDataByGroup()
{
$data = [];
foreach ($this->getSeoData() as $key => $value) {
list($group, $name) = explode('_', $key);
$data[$group][$name] = $value;
}
return $data;
}
/**
* Get all groups holding the Seo data.
*
* @return array
*/
public function getSeoGroups()
{
return array_keys($this->getSeoDataByGroup());
}
/**
* @param \ProcessWire\Field $field
*
* @return array
*/
public function getDefaultConfig(Field $field)
{
return [
'meta_title' => '{title}',
'opengraph_title' => sprintf('{%s.meta.title}', $field->name),
'opengraph_description' => sprintf('{%s.meta.description}', $field->name),
'opengraph_type' => 'website',
'twitter_card' => 'summary',
'sitemap_include' => 1,
'sitemap_priority' => '0.5',
'sitemap_changeFrequency' => 'monthly',
'structuredData_breadcrumb' => 1,
];
}
/**
* {@inheritdoc}
*/
public function ___getConfigInputfields(Field $field)
{
$wrapper = parent::___getConfigInputfields($field);
// Add a fieldset to configure the default meta data and sitemap settings.
$defaultValues = $this->wire('modules')->get('InputfieldFieldset');
$defaultValues->label = $this->_('Default Values');
$defaultValues->collapsed = Inputfield::collapsedYes;
$defaultValues->attr('name', 'default_values');
$defaultValues->description = $this->_('Set default values for your meta data and the sitemap behaviour. Each page inherits these values by default, but content editors may override them individually when editing pages. For text based metatags, enter a string or a placeholder in the form of `{field_name}` to reuse the value of an existing field. For example, the placeholder `{title}` would substitute the value of the `title` field.');
$defaultValues->notes = $this->_('Any of the default values might be overridden per template by editing this field in the context of a template.');
$defaultValues->textformatters = ['TextformatterMarkdownExtra'];
$values = array_merge($this->getDefaultConfig($field), $field->getArray());
$formManager = new FormManager($this->seoMaestro());
$form = $formManager->buildForm($this->getSeoData());
$this->alterMetaDataDefaultValuesConfigForm($form);
$formManager->populateValues($form, $values);
$defaultValues->import($form->children());
$wrapper->append($defaultValues);
// Add a second fieldset "webmaster tools" to set Google/Bing verification codes.
$webmasterTools = $this->wire('modules')->get('InputfieldFieldset');
$webmasterTools->label = $this->_('Webmaster Tools');
$webmasterTools->description = $this->_('Verify your site with search engines to help you track how well they are crawling your site.');
$webmasterTools->collapsed = Inputfield::collapsedYes;
$webmasterTools->attr('name', 'webmaster_tools');
$google = $this->wire('modules')->get('InputfieldText');
$google->attr('name', 'webmaster_tools_google_code');
$google->attr('value', $values['webmaster_tools_google_code'] ?? '');
$google->label = $this->_('Google Verification Code');
$google->description = $this->_('Get your Google verification code in the [Google Search Console](https://search.google.com/search-console).');
$webmasterTools->append($google);
$bing = $this->wire('modules')->get('InputfieldText');
$bing->attr('name', 'webmaster_tools_bing_code');
$bing->attr('value', $values['webmaster_tools_bing_code'] ?? '');
$bing->label = $this->_('Bing Verification Code');
$bing->description = $this->_('Get your Bing verification code in the [Bing Webmaster Tools](https://www.bing.com/toolbox/webmaster).');
$webmasterTools->append($bing);
$wrapper->append($webmasterTools);
return $wrapper;
}
/**
* {@inheritdoc}
*/
public function ___getConfigAdvancedInputfields(Field $field)
{
$wrapper = parent::___getConfigAdvancedInputfields($field);
$wrapper->remove($wrapper->get('autojoin'));
return $wrapper;
}
/**
* {@inheritdoc}
*/
public function ___getConfigAllowContext(Field $field)
{
return array_merge(
parent::___getConfigAllowContext($field),
[
'default_values',
'webmaster_tools'
]
);
}
private function alterMetaDataDefaultValuesConfigForm(InputfieldWrapper $form)
{
// Remove the canonical URL, there is no need to setup a default value.
$form->remove('meta_canonicalUrl');
// Add the possibility to set a thumbnail size for the opengraph image.
$opengraphImage = $form->get('opengraph_image');
$opengraphImage->columnWidth = 50;
$width = $this->wire('modules')->get('InputfieldInteger');
$width->attr('name', 'opengraph_image_width');
$width->label = $this->_('Image Width');
$width->description = $this->_('Optionally specify the width when referencing an image field.');
$width->columnWidth = 25;
$form->insertAfter($width, $opengraphImage);
$height = $this->wire('modules')->get('InputfieldInteger');
$height->attr('name', 'opengraph_image_height');
$height->label = $this->_('Image Height');
$height->description = $this->_('Optionally specify the height when referencing an image field.');
$height->columnWidth = 25;
$form->insertAfter($height, $width);
// Meta title format.
$metaTitle = $form->get('meta_title');
$metaTitleFormat = $this->wire('modules')->get('InputfieldText');
$metaTitleFormat->attr('name', 'meta_title_format');
$metaTitleFormat->label = $this->_('Title Format');
$metaTitleFormat->description = $this->_('Optionally append additional information to the rendered title. A common pattern is to include the site name or domain. Use the `{meta_title}` placeholder to substitute the rendered title, for example `{meta_title} | acme.com`.');
$metaTitleFormat->useLanguages = true;
$form->insertAfter($metaTitleFormat, $metaTitle);
}
/**
* @return \ProcessWire\SeoMaestro
*/
private function seoMaestro()
{
return $this->wire('modules')->get('SeoMaestro');
}
/**
* Check if all data of the given group is inherited in the given page value.
*
* @param string $group
* @param \SeoMaestro\PageFieldValue $pageValue
*
* @return bool
*/
private function doesGroupInheritData($group, PageFieldValue $pageValue)
{
$data = $this->getSeoDataByGroup()[$group];
foreach (array_keys($data) as $name) {
if ($pageValue->get(sprintf('%s_%s', $group, $name)) !== 'inherit') {
return false;
}
}
return true;
}
}