1 | <?php |
||||||
2 | /** |
||||||
3 | * SEOmatic plugin for Craft CMS |
||||||
4 | * |
||||||
5 | * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful, |
||||||
6 | * and flexible |
||||||
7 | * |
||||||
8 | * @link https://nystudio107.com |
||||||
9 | * @copyright Copyright (c) 2022 nystudio107 |
||||||
10 | */ |
||||||
11 | |||||||
12 | namespace nystudio107\seomatic; |
||||||
13 | |||||||
14 | use Craft; |
||||||
15 | use craft\base\Element; |
||||||
16 | use craft\base\ElementInterface; |
||||||
17 | use craft\base\Plugin; |
||||||
18 | use craft\elements\Entry; |
||||||
19 | use craft\elements\User; |
||||||
20 | use craft\errors\SiteNotFoundException; |
||||||
21 | use craft\events\DefineGqlTypeFieldsEvent; |
||||||
22 | use craft\events\ElementEvent; |
||||||
23 | use craft\events\ModelEvent; |
||||||
24 | use craft\events\PluginEvent; |
||||||
25 | use craft\events\RegisterCacheOptionsEvent; |
||||||
26 | use craft\events\RegisterComponentTypesEvent; |
||||||
27 | use craft\events\RegisterGqlQueriesEvent; |
||||||
28 | use craft\events\RegisterGqlSchemaComponentsEvent; |
||||||
29 | use craft\events\RegisterGqlTypesEvent; |
||||||
30 | use craft\events\RegisterPreviewTargetsEvent; |
||||||
31 | use craft\events\RegisterUrlRulesEvent; |
||||||
32 | use craft\events\RegisterUserPermissionsEvent; |
||||||
33 | use craft\feedme\events\RegisterFeedMeFieldsEvent; |
||||||
34 | use craft\feedme\Plugin as FeedMe; |
||||||
35 | use craft\feedme\services\Fields as FeedMeFields; |
||||||
36 | use craft\gql\TypeManager; |
||||||
37 | use craft\helpers\StringHelper; |
||||||
38 | use craft\services\Elements; |
||||||
39 | use craft\services\Fields; |
||||||
40 | use craft\services\Gql; |
||||||
41 | use craft\services\Plugins; |
||||||
42 | use craft\services\Sites as SitesService; |
||||||
43 | use craft\services\UserPermissions; |
||||||
44 | use craft\utilities\ClearCaches; |
||||||
45 | use craft\web\Application; |
||||||
46 | use craft\web\UrlManager; |
||||||
47 | use craft\web\View; |
||||||
48 | use markhuot\CraftQL\Builders\Schema; |
||||||
49 | use markhuot\CraftQL\CraftQL; |
||||||
50 | use markhuot\CraftQL\Events\AlterSchemaFields; |
||||||
51 | use nystudio107\codeeditor\autocompletes\EnvironmentVariableAutocomplete; |
||||||
52 | use nystudio107\codeeditor\events\RegisterCodeEditorAutocompletesEvent; |
||||||
53 | use nystudio107\codeeditor\events\RegisterTwigValidatorVariablesEvent; |
||||||
54 | use nystudio107\codeeditor\services\AutocompleteService; |
||||||
55 | use nystudio107\codeeditor\validators\TwigTemplateValidator; |
||||||
56 | use nystudio107\crafttwigsandbox\helpers\SecurityPolicy; |
||||||
57 | use nystudio107\crafttwigsandbox\web\SandboxView; |
||||||
58 | use nystudio107\fastcgicachebust\FastcgiCacheBust; |
||||||
59 | use nystudio107\seomatic\autocompletes\TrackingVarsAutocomplete; |
||||||
60 | use nystudio107\seomatic\debug\panels\SeomaticPanel; |
||||||
61 | use nystudio107\seomatic\fields\Seomatic_Meta as Seomatic_MetaField; |
||||||
62 | use nystudio107\seomatic\fields\SeoSettings as SeoSettingsField; |
||||||
63 | use nystudio107\seomatic\gql\arguments\SeomaticArguments; |
||||||
64 | use nystudio107\seomatic\gql\interfaces\SeomaticInterface; |
||||||
65 | use nystudio107\seomatic\gql\queries\SeomaticQuery; |
||||||
66 | use nystudio107\seomatic\gql\resolvers\SeomaticResolver; |
||||||
67 | use nystudio107\seomatic\gql\types\SeomaticEnvironmentType; |
||||||
68 | use nystudio107\seomatic\helpers\Environment as EnvironmentHelper; |
||||||
69 | use nystudio107\seomatic\helpers\Gql as GqlHelper; |
||||||
70 | use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper; |
||||||
71 | use nystudio107\seomatic\helpers\Schema as SchemaHelper; |
||||||
72 | use nystudio107\seomatic\helpers\UrlHelper; |
||||||
73 | use nystudio107\seomatic\integrations\feedme\SeoSettings as SeoSettingsFeedMe; |
||||||
74 | use nystudio107\seomatic\listeners\GetCraftQLSchema; |
||||||
75 | use nystudio107\seomatic\models\MetaScriptContainer; |
||||||
76 | use nystudio107\seomatic\models\Settings; |
||||||
77 | use nystudio107\seomatic\services\ServicesTrait; |
||||||
78 | use nystudio107\seomatic\twigextensions\SeomaticTwigExtension; |
||||||
79 | use nystudio107\seomatic\variables\SeomaticVariable; |
||||||
80 | use yii\base\Application as BaseApplication; |
||||||
81 | use yii\base\Event; |
||||||
82 | use yii\debug\Module; |
||||||
83 | |||||||
84 | /** @noinspection MissingPropertyAnnotationsInspection */ |
||||||
85 | |||||||
86 | /** |
||||||
87 | * Class Seomatic |
||||||
88 | * |
||||||
89 | * @author nystudio107 |
||||||
90 | * @package Seomatic |
||||||
91 | * @since 3.0.0 |
||||||
92 | */ |
||||||
93 | class Seomatic extends Plugin |
||||||
94 | { |
||||||
95 | // Traits |
||||||
96 | // ========================================================================= |
||||||
97 | |||||||
98 | use ServicesTrait; |
||||||
99 | |||||||
100 | // Constants |
||||||
101 | // ========================================================================= |
||||||
102 | |||||||
103 | const SEOMATIC_HANDLE = 'Seomatic'; |
||||||
104 | |||||||
105 | const DEVMODE_CACHE_DURATION = 30; |
||||||
106 | |||||||
107 | const FRONTEND_SEO_FILE_LINK = 'seomatic/seo-file-link/<url:[^\/]+>/<robots:[^\/]+>/<canonical:[^\/]+>/<inline:\d+>/<fileName:[-\w\.*]+>'; |
||||||
108 | |||||||
109 | const FRONTEND_PREVIEW_PATH = 'seomatic/preview-social-media'; |
||||||
110 | |||||||
111 | const SEOMATIC_PREVIEW_AUTHORIZATION_KEY = 'seomaticPreviewAuthorizationKey'; |
||||||
112 | |||||||
113 | const GQL_ELEMENT_INTERFACES = [ |
||||||
114 | 'EntryInterface', |
||||||
115 | 'CategoryInterface', |
||||||
116 | 'ProductInterface', |
||||||
117 | ]; |
||||||
118 | |||||||
119 | const SEOMATIC_EXPRESSION_FIELD_TYPE = 'SeomaticExpressionField'; |
||||||
120 | const SEOMATIC_TRACKING_FIELD_TYPE = 'SeomaticTrackingField'; |
||||||
121 | |||||||
122 | // Static Properties |
||||||
123 | // ========================================================================= |
||||||
124 | |||||||
125 | /** |
||||||
126 | * @var Seomatic |
||||||
127 | */ |
||||||
128 | public static $plugin; |
||||||
129 | |||||||
130 | /** |
||||||
131 | * @var SeomaticVariable|null |
||||||
132 | */ |
||||||
133 | public static $seomaticVariable; |
||||||
134 | |||||||
135 | /** |
||||||
136 | * @var Settings |
||||||
137 | */ |
||||||
138 | public static $settings; |
||||||
139 | |||||||
140 | /** |
||||||
141 | * @var ElementInterface|null |
||||||
142 | */ |
||||||
143 | public static $matchedElement; |
||||||
144 | |||||||
145 | /** |
||||||
146 | * @var bool |
||||||
147 | */ |
||||||
148 | public static $devMode; |
||||||
149 | |||||||
150 | /** |
||||||
151 | * @var View |
||||||
152 | */ |
||||||
153 | public static $view; |
||||||
154 | |||||||
155 | /** |
||||||
156 | * @var null|View |
||||||
157 | */ |
||||||
158 | public static $sandboxView; |
||||||
159 | |||||||
160 | /** |
||||||
161 | * @var string |
||||||
162 | */ |
||||||
163 | public static $language; |
||||||
164 | |||||||
165 | /** |
||||||
166 | * @var string |
||||||
167 | */ |
||||||
168 | public static $environment; |
||||||
169 | |||||||
170 | /** |
||||||
171 | * @var int |
||||||
172 | */ |
||||||
173 | public static $cacheDuration; |
||||||
174 | |||||||
175 | /** |
||||||
176 | * @var bool |
||||||
177 | */ |
||||||
178 | public static $previewingMetaContainers = false; |
||||||
179 | |||||||
180 | /** |
||||||
181 | * @var bool |
||||||
182 | */ |
||||||
183 | public static $loadingMetaContainers = false; |
||||||
184 | |||||||
185 | /** |
||||||
186 | * @var bool |
||||||
187 | */ |
||||||
188 | public static $savingSettings = false; |
||||||
189 | |||||||
190 | /** |
||||||
191 | * @var bool |
||||||
192 | */ |
||||||
193 | public static $headlessRequest = false; |
||||||
194 | |||||||
195 | /** |
||||||
196 | * @var bool |
||||||
197 | */ |
||||||
198 | public static $craft31 = false; |
||||||
199 | |||||||
200 | /** |
||||||
201 | * @var bool |
||||||
202 | */ |
||||||
203 | public static $craft32 = false; |
||||||
204 | |||||||
205 | /** |
||||||
206 | * @var bool |
||||||
207 | */ |
||||||
208 | public static $craft33 = false; |
||||||
209 | |||||||
210 | /** |
||||||
211 | * @var bool |
||||||
212 | */ |
||||||
213 | public static $craft34 = false; |
||||||
214 | |||||||
215 | /** |
||||||
216 | * @var bool |
||||||
217 | */ |
||||||
218 | public static $craft35 = false; |
||||||
219 | |||||||
220 | /** |
||||||
221 | * @var bool |
||||||
222 | */ |
||||||
223 | public static $craft37 = false; |
||||||
224 | |||||||
225 | /** |
||||||
226 | * @var string |
||||||
227 | */ |
||||||
228 | public $schemaVersion = '3.0.12'; |
||||||
229 | /** |
||||||
230 | * @var bool |
||||||
231 | */ |
||||||
232 | public $hasCpSection = true; |
||||||
233 | |||||||
234 | // Public Properties |
||||||
235 | // ========================================================================= |
||||||
236 | |||||||
237 | /** |
||||||
238 | * @var bool |
||||||
239 | */ |
||||||
240 | public $hasCpSettings = true; |
||||||
241 | |||||||
242 | // Static Methods |
||||||
243 | // ========================================================================= |
||||||
244 | |||||||
245 | /** |
||||||
246 | * Set the matched element |
||||||
247 | * |
||||||
248 | * @param $element null|ElementInterface |
||||||
249 | */ |
||||||
250 | public static function setMatchedElement($element) |
||||||
251 | { |
||||||
252 | self::$matchedElement = $element; |
||||||
253 | /** @var $element Element */ |
||||||
254 | if ($element) { |
||||||
0 ignored issues
–
show
introduced
by
![]() |
|||||||
255 | self::$language = MetaValueHelper::getSiteLanguage($element->siteId); |
||||||
256 | } else { |
||||||
257 | self::$language = MetaValueHelper::getSiteLanguage(null); |
||||||
258 | } |
||||||
259 | MetaValueHelper::cache(); |
||||||
260 | } |
||||||
261 | |||||||
262 | // Public Methods |
||||||
263 | // ========================================================================= |
||||||
264 | |||||||
265 | /** |
||||||
266 | * @inheritdoc |
||||||
267 | */ |
||||||
268 | public function init() |
||||||
269 | { |
||||||
270 | parent::init(); |
||||||
271 | self::$plugin = $this; |
||||||
272 | // Handle any console commands |
||||||
273 | $request = Craft::$app->getRequest(); |
||||||
274 | if ($request->getIsConsoleRequest()) { |
||||||
275 | $this->controllerNamespace = 'nystudio107\seomatic\console\controllers'; |
||||||
276 | } |
||||||
277 | // Initialize properties |
||||||
278 | /** @var Settings $settings */ |
||||||
279 | $settings = self::$plugin->getSettings(); |
||||||
280 | self::$settings = $settings; |
||||||
281 | self::$devMode = Craft::$app->getConfig()->getGeneral()->devMode; |
||||||
282 | self::$view = Craft::$app->getView(); |
||||||
283 | // Use a Twig sandbox for SEOmatic rendering |
||||||
284 | $securityPolicy = SecurityPolicy::createFromFile('seomatic-sandbox', '@nystudio107/seomatic'); |
||||||
285 | self::$sandboxView = new SandboxView(['securityPolicy' => $securityPolicy]); |
||||||
286 | self::$cacheDuration = self::$devMode |
||||||
0 ignored issues
–
show
It seems like
self::devMode ? self::DE...taCacheDuration ?? null can also be of type string . However, the property $cacheDuration is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
287 | ? self::DEVMODE_CACHE_DURATION |
||||||
288 | : self::$settings->metaCacheDuration ?? null; |
||||||
289 | self::$cacheDuration = self::$cacheDuration === null ? null : (int)self::$cacheDuration; |
||||||
290 | self::$environment = EnvironmentHelper::determineEnvironment(); |
||||||
291 | MetaValueHelper::cache(); |
||||||
292 | // Version helpers |
||||||
293 | self::$craft31 = version_compare(Craft::$app->getVersion(), '3.1', '>='); |
||||||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.1', '>=') can also be of type integer . However, the property $craft31 is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
294 | self::$craft32 = version_compare(Craft::$app->getVersion(), '3.2', '>='); |
||||||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.2', '>=') can also be of type integer . However, the property $craft32 is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
295 | self::$craft33 = version_compare(Craft::$app->getVersion(), '3.3', '>='); |
||||||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.3', '>=') can also be of type integer . However, the property $craft33 is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
296 | self::$craft34 = version_compare(Craft::$app->getVersion(), '3.4', '>='); |
||||||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.4', '>=') can also be of type integer . However, the property $craft34 is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
297 | self::$craft35 = version_compare(Craft::$app->getVersion(), '3.5', '>='); |
||||||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.5', '>=') can also be of type integer . However, the property $craft35 is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
298 | self::$craft37 = version_compare(Craft::$app->getVersion(), '3.7', '>='); |
||||||
0 ignored issues
–
show
It seems like
version_compare(Craft::a...Version(), '3.7', '>=') can also be of type integer . However, the property $craft37 is declared as type boolean . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
299 | $this->name = self::$settings->pluginName; |
||||||
300 | // Install our event listeners |
||||||
301 | $this->installEventListeners(); |
||||||
302 | // We're loaded |
||||||
303 | Craft::info( |
||||||
304 | Craft::t( |
||||||
305 | 'seomatic', |
||||||
306 | '{name} plugin loaded', |
||||||
307 | ['name' => $this->name] |
||||||
308 | ), |
||||||
309 | __METHOD__ |
||||||
310 | ); |
||||||
311 | } |
||||||
312 | |||||||
313 | /** |
||||||
314 | * @inheritdoc |
||||||
315 | */ |
||||||
316 | public function getSettings() |
||||||
317 | { |
||||||
318 | // For all the emojis |
||||||
319 | $settingsModel = parent::getSettings(); |
||||||
320 | if ($settingsModel !== null && !self::$savingSettings) { |
||||||
321 | $attributes = $settingsModel->attributes(); |
||||||
322 | if ($attributes !== null) { |
||||||
0 ignored issues
–
show
|
|||||||
323 | foreach ($attributes as $attribute) { |
||||||
324 | if (is_string($settingsModel->$attribute)) { |
||||||
325 | $settingsModel->$attribute = html_entity_decode( |
||||||
326 | $settingsModel->$attribute, |
||||||
327 | ENT_NOQUOTES, |
||||||
328 | 'UTF-8' |
||||||
329 | ); |
||||||
330 | } |
||||||
331 | } |
||||||
332 | } |
||||||
333 | self::$savingSettings = false; |
||||||
334 | } |
||||||
335 | |||||||
336 | /* @var Settings $settingsModel */ |
||||||
337 | return $settingsModel; |
||||||
338 | } |
||||||
339 | |||||||
340 | /** |
||||||
341 | * Determine whether our table schema exists or not; this is needed because |
||||||
342 | * migrations such as the install migration and base_install migration may |
||||||
343 | * not have been run by the time our init() method has been called |
||||||
344 | * |
||||||
345 | * @return bool |
||||||
346 | */ |
||||||
347 | public function migrationsAndSchemaReady(): bool |
||||||
348 | { |
||||||
349 | $pluginsService = Craft::$app->getPlugins(); |
||||||
350 | if ($pluginsService->doesPluginRequireDatabaseUpdate(self::$plugin)) { |
||||||
351 | return false; |
||||||
352 | } |
||||||
353 | if (Craft::$app->db->schema->getTableSchema('{{%seomatic_metabundles}}') === null) { |
||||||
354 | return false; |
||||||
355 | } |
||||||
356 | |||||||
357 | return true; |
||||||
358 | } |
||||||
359 | |||||||
360 | /** |
||||||
361 | * Clear all the caches! |
||||||
362 | */ |
||||||
363 | public function clearAllCaches() |
||||||
364 | { |
||||||
365 | // Clear all of SEOmatic's caches |
||||||
366 | self::$plugin->frontendTemplates->invalidateCaches(); |
||||||
367 | self::$plugin->metaContainers->invalidateCaches(); |
||||||
368 | self::$plugin->sitemaps->invalidateCaches(); |
||||||
369 | SchemaHelper::invalidateCaches(); |
||||||
370 | // If they are using Craft 3.3 or later, clear the GraphQL caches too |
||||||
371 | if (self::$craft33) { |
||||||
372 | $gql = Craft::$app->getGql(); |
||||||
373 | if (method_exists($gql, 'invalidateCaches')) { |
||||||
374 | $gql->invalidateCaches(); |
||||||
375 | } |
||||||
376 | } |
||||||
377 | // If the FastCGI Cache Bust plugin is installed, clear its caches too |
||||||
378 | /** @var ?FastcgiCacheBust $plugin */ |
||||||
379 | $plugin = Craft::$app->getPlugins()->getPlugin('fastcgi-cache-bust'); |
||||||
380 | if ($plugin !== null) { |
||||||
381 | $plugin->cache->clearAll(); |
||||||
382 | } |
||||||
383 | } |
||||||
384 | |||||||
385 | // Protected Methods |
||||||
386 | // ========================================================================= |
||||||
387 | |||||||
388 | /** |
||||||
389 | * @inheritdoc |
||||||
390 | */ |
||||||
391 | public function getSettingsResponse() |
||||||
392 | { |
||||||
393 | // Just redirect to the plugin settings page |
||||||
394 | Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('seomatic/plugin')); |
||||||
395 | } |
||||||
396 | |||||||
397 | /** |
||||||
398 | * @inheritdoc |
||||||
399 | */ |
||||||
400 | public function getCpNavItem() |
||||||
401 | { |
||||||
402 | $subNavs = []; |
||||||
403 | $navItem = parent::getCpNavItem(); |
||||||
404 | $request = Craft::$app->getRequest(); |
||||||
405 | $siteSuffix = ''; |
||||||
406 | if ($request->getSegment(1) === 'seomatic') { |
||||||
407 | $segments = $request->getSegments(); |
||||||
408 | $lastSegment = end($segments); |
||||||
409 | $site = Craft::$app->getSites()->getSiteByHandle($lastSegment); |
||||||
410 | if ($site !== null) { |
||||||
411 | $siteSuffix = '/' . $lastSegment; |
||||||
412 | } |
||||||
413 | } |
||||||
414 | /** @var User $currentUser */ |
||||||
415 | $currentUser = Craft::$app->getUser()->getIdentity(); |
||||||
416 | // Only show sub-navs the user has permission to view |
||||||
417 | if ($currentUser->can('seomatic:dashboard')) { |
||||||
418 | $subNavs['dashboard'] = [ |
||||||
419 | 'label' => Craft::t('seomatic', 'Dashboard'), |
||||||
420 | 'url' => 'seomatic/dashboard' . $siteSuffix, |
||||||
421 | ]; |
||||||
422 | } |
||||||
423 | if ($currentUser->can('seomatic:global-meta')) { |
||||||
424 | $subNavs['global'] = [ |
||||||
425 | 'label' => Craft::t('seomatic', 'Global SEO'), |
||||||
426 | 'url' => 'seomatic/global/general' . $siteSuffix, |
||||||
427 | ]; |
||||||
428 | } |
||||||
429 | if ($currentUser->can('seomatic:content-meta')) { |
||||||
430 | $subNavs['content'] = [ |
||||||
431 | 'label' => Craft::t('seomatic', 'Content SEO'), |
||||||
432 | 'url' => 'seomatic/content' . $siteSuffix, |
||||||
433 | ]; |
||||||
434 | } |
||||||
435 | if ($currentUser->can('seomatic:site-settings')) { |
||||||
436 | $subNavs['site'] = [ |
||||||
437 | 'label' => Craft::t('seomatic', 'Site Settings'), |
||||||
438 | 'url' => 'seomatic/site/identity' . $siteSuffix, |
||||||
439 | ]; |
||||||
440 | } |
||||||
441 | if ($currentUser->can('seomatic:tracking-scripts')) { |
||||||
442 | $subNavs['tracking'] = [ |
||||||
443 | 'label' => Craft::t('seomatic', 'Tracking Scripts'), |
||||||
444 | 'url' => 'seomatic/tracking/gtag' . $siteSuffix, |
||||||
445 | ]; |
||||||
446 | } |
||||||
447 | $editableSettings = true; |
||||||
448 | $general = Craft::$app->getConfig()->getGeneral(); |
||||||
449 | if (self::$craft31 && !$general->allowAdminChanges) { |
||||||
450 | $editableSettings = false; |
||||||
451 | } |
||||||
452 | if ($currentUser->can('seomatic:plugin-settings') && $editableSettings) { |
||||||
453 | $subNavs['plugin'] = [ |
||||||
454 | 'label' => Craft::t('seomatic', 'Plugin Settings'), |
||||||
455 | 'url' => 'seomatic/plugin', |
||||||
456 | ]; |
||||||
457 | } |
||||||
458 | // SEOmatic doesn't really have an index page, so if the user can't access any sub nav items, we probably shouldn't show the main sub nav item either |
||||||
459 | if (empty($subNavs)) { |
||||||
460 | return null; |
||||||
461 | } |
||||||
462 | // A single sub nav item is redundant |
||||||
463 | if (count($subNavs) === 1) { |
||||||
464 | $subNavs = []; |
||||||
465 | } |
||||||
466 | $navItem = array_merge($navItem, [ |
||||||
467 | 'subnav' => $subNavs, |
||||||
468 | ]); |
||||||
469 | |||||||
470 | return $navItem; |
||||||
471 | } |
||||||
472 | |||||||
473 | /** |
||||||
474 | * Install our event listeners. |
||||||
475 | */ |
||||||
476 | protected function installEventListeners() |
||||||
477 | { |
||||||
478 | // Install our event listeners only if our table schema exists |
||||||
479 | if ($this->migrationsAndSchemaReady()) { |
||||||
480 | // Add in our Twig extensions |
||||||
481 | $seomaticTwigExtension = new SeomaticTwigExtension(); |
||||||
482 | self::$view->registerTwigExtension($seomaticTwigExtension); |
||||||
483 | self::$sandboxView->registerTwigExtension($seomaticTwigExtension); |
||||||
0 ignored issues
–
show
The method
registerTwigExtension() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
484 | $request = Craft::$app->getRequest(); |
||||||
485 | // Add in our event listeners that are needed for every request |
||||||
486 | $this->installGlobalEventListeners(); |
||||||
487 | // Install only for non-console site requests |
||||||
488 | if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) { |
||||||
489 | $this->installSiteEventListeners(); |
||||||
490 | } |
||||||
491 | // Install only for non-console Control Panel requests |
||||||
492 | if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) { |
||||||
493 | $this->installCpEventListeners(); |
||||||
494 | } |
||||||
495 | } |
||||||
496 | // Handler: EVENT_AFTER_INSTALL_PLUGIN |
||||||
497 | Event::on( |
||||||
498 | Plugins::class, |
||||||
499 | Plugins::EVENT_AFTER_INSTALL_PLUGIN, |
||||||
500 | function(PluginEvent $event) { |
||||||
501 | if ($event->plugin === $this) { |
||||||
502 | // Invalidate our caches after we've been installed |
||||||
503 | $this->clearAllCaches(); |
||||||
504 | // Send them to our welcome screen |
||||||
505 | $request = Craft::$app->getRequest(); |
||||||
506 | if ($request->isCpRequest) { |
||||||
507 | Craft::$app->getResponse()->redirect(UrlHelper::cpUrl( |
||||||
508 | 'seomatic/dashboard', |
||||||
509 | [ |
||||||
510 | 'showWelcome' => true, |
||||||
511 | ] |
||||||
512 | ))->send(); |
||||||
513 | } |
||||||
514 | } |
||||||
515 | } |
||||||
516 | ); |
||||||
517 | // Handler: ClearCaches::EVENT_REGISTER_CACHE_OPTIONS |
||||||
518 | Event::on( |
||||||
519 | ClearCaches::class, |
||||||
520 | ClearCaches::EVENT_REGISTER_CACHE_OPTIONS, |
||||||
521 | function(RegisterCacheOptionsEvent $event) { |
||||||
522 | Craft::debug( |
||||||
523 | 'ClearCaches::EVENT_REGISTER_CACHE_OPTIONS', |
||||||
524 | __METHOD__ |
||||||
525 | ); |
||||||
526 | // Register our Cache Options |
||||||
527 | $event->options = array_merge( |
||||||
528 | $event->options, |
||||||
529 | $this->customAdminCpCacheOptions() |
||||||
530 | ); |
||||||
531 | } |
||||||
532 | ); |
||||||
533 | // Handler: EVENT_BEFORE_SAVE_PLUGIN_SETTINGS |
||||||
534 | Event::on( |
||||||
535 | Plugins::class, |
||||||
536 | Plugins::EVENT_BEFORE_SAVE_PLUGIN_SETTINGS, |
||||||
537 | function(PluginEvent $event) { |
||||||
538 | if ($event->plugin === $this && !Craft::$app->getDb()->getSupportsMb4()) { |
||||||
539 | // For all the emojis |
||||||
540 | $settingsModel = $this->getSettings(); |
||||||
541 | self::$savingSettings = true; |
||||||
542 | if ($settingsModel !== null) { |
||||||
543 | $attributes = $settingsModel->attributes(); |
||||||
544 | if ($attributes !== null) { |
||||||
545 | foreach ($attributes as $attribute) { |
||||||
546 | if (is_string($settingsModel->$attribute)) { |
||||||
547 | $settingsModel->$attribute = |
||||||
548 | StringHelper::encodeMb4($settingsModel->$attribute); |
||||||
549 | } |
||||||
550 | } |
||||||
551 | } |
||||||
552 | } |
||||||
553 | } |
||||||
554 | } |
||||||
555 | ); |
||||||
556 | } |
||||||
557 | |||||||
558 | /** |
||||||
559 | * Install global event listeners for all request types |
||||||
560 | */ |
||||||
561 | protected function installGlobalEventListeners() |
||||||
562 | { |
||||||
563 | // Handler: Plugins::EVENT_AFTER_LOAD_PLUGINS |
||||||
564 | Event::on( |
||||||
565 | Plugins::class, |
||||||
566 | Plugins::EVENT_AFTER_LOAD_PLUGINS, |
||||||
567 | function() { |
||||||
568 | // Delay registering SEO Elements to give other plugins a chance to load first |
||||||
569 | $this->seoElements->getAllSeoElementTypes(false); |
||||||
570 | // Delay installing GQL handlers to give other plugins a chance to register their own first |
||||||
571 | $this->installGqlHandlers(); |
||||||
572 | // Install these only after all other plugins have loaded |
||||||
573 | $request = Craft::$app->getRequest(); |
||||||
574 | // Only respond to non-console site requests |
||||||
575 | if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) { |
||||||
576 | $this->handleSiteRequest(); |
||||||
577 | } |
||||||
578 | // Respond to Control Panel requests |
||||||
579 | if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) { |
||||||
580 | $this->handleAdminCpRequest(); |
||||||
581 | } |
||||||
582 | } |
||||||
583 | ); |
||||||
584 | // Handler: Fields::EVENT_REGISTER_FIELD_TYPES |
||||||
585 | Event::on( |
||||||
586 | Fields::class, |
||||||
587 | Fields::EVENT_REGISTER_FIELD_TYPES, |
||||||
588 | function(RegisterComponentTypesEvent $event) { |
||||||
589 | $event->types[] = SeoSettingsField::class; |
||||||
590 | $event->types[] = Seomatic_MetaField::class; |
||||||
591 | } |
||||||
592 | ); |
||||||
593 | // Handler: Element::EVENT_AFTER_PROPAGATE |
||||||
594 | Event::on( |
||||||
595 | Element::class, |
||||||
596 | Element::EVENT_AFTER_PROPAGATE, |
||||||
597 | static function(ModelEvent $event) { |
||||||
598 | Craft::debug( |
||||||
599 | 'Element::EVENT_AFTER_PROPAGATE', |
||||||
600 | __METHOD__ |
||||||
601 | ); |
||||||
602 | /** @var Element $element */ |
||||||
603 | $element = $event->sender; |
||||||
604 | self::$plugin->metaBundles->invalidateMetaBundleByElement( |
||||||
605 | $element, |
||||||
606 | $event->isNew |
||||||
607 | ); |
||||||
608 | if ($event->isNew) { |
||||||
609 | self::$plugin->sitemaps->submitSitemapForElement($element); |
||||||
610 | } |
||||||
611 | } |
||||||
612 | ); |
||||||
613 | // Handler: Elements::EVENT_AFTER_DELETE_ELEMENT |
||||||
614 | Event::on( |
||||||
615 | Elements::class, |
||||||
616 | Elements::EVENT_AFTER_DELETE_ELEMENT, |
||||||
617 | function(ElementEvent $event) { |
||||||
618 | Craft::debug( |
||||||
619 | 'Elements::EVENT_AFTER_DELETE_ELEMENT', |
||||||
620 | __METHOD__ |
||||||
621 | ); |
||||||
622 | /** @var Element $element */ |
||||||
623 | $element = $event->element; |
||||||
624 | self::$plugin->metaBundles->invalidateMetaBundleByElement( |
||||||
625 | $element, |
||||||
626 | false |
||||||
627 | ); |
||||||
628 | } |
||||||
629 | ); |
||||||
630 | // Add social media preview targets on Craft 3.2 or later |
||||||
631 | if (self::$craft32 && Seomatic::$settings->socialMediaPreviewTarget) { |
||||||
632 | // Handler: Entry::EVENT_REGISTER_PREVIEW_TARGETS |
||||||
633 | Event::on( |
||||||
634 | Entry::class, |
||||||
635 | Entry::EVENT_REGISTER_PREVIEW_TARGETS, |
||||||
636 | function(RegisterPreviewTargetsEvent $e) { |
||||||
637 | /** @var Element $element */ |
||||||
638 | $element = $e->sender; |
||||||
639 | if ($element->uri !== null) { |
||||||
640 | $e->previewTargets[] = [ |
||||||
641 | 'label' => '📣 ' . Craft::t('seomatic', 'Social Media Preview'), |
||||||
642 | 'url' => UrlHelper::siteUrl(self::FRONTEND_PREVIEW_PATH, [ |
||||||
643 | 'elementId' => $element->id, |
||||||
644 | 'siteId' => $element->siteId, |
||||||
645 | ]), |
||||||
646 | ]; |
||||||
647 | // Don't allow the preview to be accessed publicly |
||||||
648 | Craft::$app->getSession()->authorize(self::SEOMATIC_PREVIEW_AUTHORIZATION_KEY . $element->id); |
||||||
649 | } |
||||||
650 | } |
||||||
651 | ); |
||||||
652 | } |
||||||
653 | // Yii2 Debug Toolbar support |
||||||
654 | Event::on( |
||||||
655 | Application::class, |
||||||
656 | BaseApplication::EVENT_BEFORE_REQUEST, |
||||||
657 | static function() { |
||||||
658 | /** @var Module|null $debugModule */ |
||||||
659 | $debugModule = Seomatic::$settings->enableDebugToolbarPanel ? Craft::$app->getModule('debug') : null; |
||||||
660 | |||||||
661 | if ($debugModule) { |
||||||
0 ignored issues
–
show
|
|||||||
662 | $debugModule->panels['seomatic'] = new SeomaticPanel([ |
||||||
663 | 'id' => 'seomatic', |
||||||
664 | 'module' => $debugModule, |
||||||
665 | ]); |
||||||
666 | } |
||||||
667 | } |
||||||
668 | ); |
||||||
669 | // FeedMe Support |
||||||
670 | if (class_exists(FeedMe::class)) { |
||||||
671 | Event::on( |
||||||
672 | FeedMeFields::class, |
||||||
673 | FeedMeFields::EVENT_REGISTER_FEED_ME_FIELDS, |
||||||
674 | function(RegisterFeedMeFieldsEvent $e) { |
||||||
675 | Craft::debug( |
||||||
676 | 'FeedMeFields::EVENT_REGISTER_FEED_ME_FIELDS', |
||||||
677 | __METHOD__ |
||||||
678 | ); |
||||||
679 | $e->fields[] = SeoSettingsFeedMe::class; |
||||||
680 | } |
||||||
681 | ); |
||||||
682 | } |
||||||
683 | $updateMetaBundles = function($message) { |
||||||
684 | Craft::debug( |
||||||
685 | $message, |
||||||
686 | __METHOD__ |
||||||
687 | ); |
||||||
688 | $seoElementTypes = Seomatic::$plugin->seoElements->getAllSeoElementTypes(); |
||||||
689 | foreach ($seoElementTypes as $seoElementType) { |
||||||
690 | $metaBundleType = $seoElementType::META_BUNDLE_TYPE ?? ''; |
||||||
691 | |||||||
692 | if ($metaBundleType) { |
||||||
693 | Seomatic::$plugin->metaBundles->resaveMetaBundles($metaBundleType); |
||||||
694 | } |
||||||
695 | } |
||||||
696 | }; |
||||||
697 | |||||||
698 | // Handler: Elements::EVENT_AFTER_SAVE_SITE |
||||||
699 | Event::on( |
||||||
700 | SitesService::class, |
||||||
701 | SitesService::EVENT_AFTER_SAVE_SITE, |
||||||
702 | function() use ($updateMetaBundles) { |
||||||
703 | $updateMetaBundles('SitesService::EVENT_AFTER_SAVE_SITE'); |
||||||
704 | } |
||||||
705 | ); |
||||||
706 | |||||||
707 | // Handler: Elements::EVENT_AFTER_DELETE_SITE |
||||||
708 | Event::on( |
||||||
709 | SitesService::class, |
||||||
710 | SitesService::EVENT_AFTER_DELETE_SITE, |
||||||
711 | function() use ($updateMetaBundles) { |
||||||
712 | $updateMetaBundles('SitesService::EVENT_AFTER_DELETE_SITE'); |
||||||
713 | } |
||||||
714 | ); |
||||||
715 | } |
||||||
716 | |||||||
717 | /** |
||||||
718 | * Register our GraphQL handlers |
||||||
719 | * |
||||||
720 | * @return void |
||||||
721 | */ |
||||||
722 | protected function installGqlHandlers() |
||||||
723 | { |
||||||
724 | // Add native GraphQL support on Craft 3.3 or later |
||||||
725 | if (self::$craft33) { |
||||||
726 | // Handler: Gql::EVENT_REGISTER_GQL_TYPES |
||||||
727 | Event::on( |
||||||
728 | Gql::class, |
||||||
729 | Gql::EVENT_REGISTER_GQL_TYPES, |
||||||
730 | function(RegisterGqlTypesEvent $event) { |
||||||
731 | Craft::debug( |
||||||
732 | 'Gql::EVENT_REGISTER_GQL_TYPES', |
||||||
733 | __METHOD__ |
||||||
734 | ); |
||||||
735 | $event->types[] = SeomaticInterface::class; |
||||||
736 | $event->types[] = SeomaticEnvironmentType::class; |
||||||
737 | } |
||||||
738 | ); |
||||||
739 | // Handler: Gql::EVENT_REGISTER_GQL_QUERIES |
||||||
740 | Event::on( |
||||||
741 | Gql::class, |
||||||
742 | Gql::EVENT_REGISTER_GQL_QUERIES, |
||||||
743 | function(RegisterGqlQueriesEvent $event) { |
||||||
744 | Craft::debug( |
||||||
745 | 'Gql::EVENT_REGISTER_GQL_QUERIES', |
||||||
746 | __METHOD__ |
||||||
747 | ); |
||||||
748 | $queries = SeomaticQuery::getQueries(); |
||||||
749 | foreach ($queries as $key => $value) { |
||||||
750 | $event->queries[$key] = $value; |
||||||
751 | } |
||||||
752 | } |
||||||
753 | ); |
||||||
754 | if (self::$craft35) { |
||||||
755 | // Handler: Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS |
||||||
756 | Event::on( |
||||||
757 | Gql::class, |
||||||
758 | Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS, |
||||||
759 | function(RegisterGqlSchemaComponentsEvent $event) { |
||||||
760 | Craft::debug( |
||||||
761 | 'Gql::EVENT_REGISTER_GQL_SCHEMA_COMPONENTS', |
||||||
762 | __METHOD__ |
||||||
763 | ); |
||||||
764 | $label = Craft::t('seomatic', 'Seomatic'); |
||||||
765 | $event->queries[$label]['seomatic.all:read'] = ['label' => Craft::t('seomatic', 'Query Seomatic data')]; |
||||||
766 | } |
||||||
767 | ); |
||||||
768 | } |
||||||
769 | } |
||||||
770 | // Add support for querying for SEOmatic metadata inside of element queries |
||||||
771 | if (self::$craft34) { |
||||||
772 | // Handler: TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS |
||||||
773 | $knownInterfaceNames = self::$plugin->seoElements->getAllSeoElementGqlInterfaceNames(); |
||||||
774 | Event::on( |
||||||
775 | TypeManager::class, |
||||||
776 | TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS, |
||||||
777 | function(DefineGqlTypeFieldsEvent $event) use ($knownInterfaceNames) { |
||||||
778 | if (in_array($event->typeName, $knownInterfaceNames, true)) { |
||||||
779 | Craft::debug( |
||||||
780 | 'TypeManager::EVENT_DEFINE_GQL_TYPE_FIELDS', |
||||||
781 | __METHOD__ |
||||||
782 | ); |
||||||
783 | |||||||
784 | if (GqlHelper::canQuerySeo()) { |
||||||
785 | // Make Seomatic tags available to all entries. |
||||||
786 | $event->fields['seomatic'] = [ |
||||||
787 | 'name' => 'seomatic', |
||||||
788 | 'type' => SeomaticInterface::getType(), |
||||||
789 | 'args' => SeomaticArguments::getArguments(), |
||||||
790 | 'resolve' => SeomaticResolver::class . '::resolve', |
||||||
791 | 'description' => Craft::t('seomatic', 'This query is used to query for SEOmatic meta data.'), |
||||||
792 | ]; |
||||||
793 | } |
||||||
794 | } |
||||||
795 | }); |
||||||
796 | } |
||||||
797 | // CraftQL Support |
||||||
798 | if (class_exists(CraftQL::class)) { |
||||||
799 | Event::on( |
||||||
800 | Schema::class, |
||||||
801 | AlterSchemaFields::EVENT, |
||||||
802 | [GetCraftQLSchema::class, 'handle'] |
||||||
803 | ); |
||||||
804 | } |
||||||
805 | } |
||||||
806 | |||||||
807 | /** |
||||||
808 | * Handle site requests. We do it only after we receive the event |
||||||
809 | * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run |
||||||
810 | * before our event listeners kick in |
||||||
811 | */ |
||||||
812 | protected function handleSiteRequest() |
||||||
813 | { |
||||||
814 | // Handler: View::EVENT_END_PAGE |
||||||
815 | Event::on( |
||||||
816 | View::class, |
||||||
817 | View::EVENT_END_PAGE, |
||||||
818 | function() { |
||||||
819 | Craft::debug( |
||||||
820 | 'View::EVENT_END_PAGE', |
||||||
821 | __METHOD__ |
||||||
822 | ); |
||||||
823 | // The page is done rendering, include our meta containers |
||||||
824 | if (self::$settings->renderEnabled && self::$seomaticVariable) { |
||||||
825 | self::$plugin->metaContainers->includeMetaContainers(); |
||||||
826 | } |
||||||
827 | } |
||||||
828 | ); |
||||||
829 | } |
||||||
830 | |||||||
831 | /** |
||||||
832 | * Handle Control Panel requests. We do it only after we receive the event |
||||||
833 | * EVENT_AFTER_LOAD_PLUGINS so that any pending db migrations can be run |
||||||
834 | * before our event listeners kick in |
||||||
835 | */ |
||||||
836 | protected function handleAdminCpRequest() |
||||||
837 | { |
||||||
838 | // Don't cache Control Panel requests |
||||||
839 | self::$cacheDuration = 1; |
||||||
840 | // Prefix the Control Panel title |
||||||
841 | self::$view->hook('cp.layouts.base', function(&$context) { |
||||||
842 | if (self::$devMode) { |
||||||
843 | $context['docTitle'] = self::$settings->devModeCpTitlePrefix . $context['docTitle']; |
||||||
844 | } else { |
||||||
845 | $context['docTitle'] = self::$settings->cpTitlePrefix . $context['docTitle']; |
||||||
846 | } |
||||||
847 | }); |
||||||
848 | } |
||||||
849 | |||||||
850 | /** |
||||||
851 | * Install site event listeners for site requests only |
||||||
852 | */ |
||||||
853 | protected function installSiteEventListeners() |
||||||
854 | { |
||||||
855 | // Load the sitemap containers |
||||||
856 | self::$plugin->sitemaps->loadSitemapContainers(); |
||||||
857 | // Load the frontend template containers |
||||||
858 | self::$plugin->frontendTemplates->loadFrontendTemplateContainers(); |
||||||
859 | // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES |
||||||
860 | Event::on( |
||||||
861 | UrlManager::class, |
||||||
862 | UrlManager::EVENT_REGISTER_SITE_URL_RULES, |
||||||
863 | function(RegisterUrlRulesEvent $event) { |
||||||
864 | Craft::debug( |
||||||
865 | 'UrlManager::EVENT_REGISTER_SITE_URL_RULES', |
||||||
866 | __METHOD__ |
||||||
867 | ); |
||||||
868 | // FileController |
||||||
869 | $route = self::$plugin->handle . '/file/seo-file-link'; |
||||||
870 | $event->rules[self::FRONTEND_SEO_FILE_LINK] = ['route' => $route]; |
||||||
871 | // PreviewController |
||||||
872 | $route = self::$plugin->handle . '/preview/social-media'; |
||||||
873 | $event->rules[self::FRONTEND_PREVIEW_PATH] = ['route' => $route]; |
||||||
874 | // Register our Control Panel routes |
||||||
875 | $event->rules = array_merge( |
||||||
876 | $event->rules, |
||||||
877 | $this->customFrontendRoutes() |
||||||
878 | ); |
||||||
879 | } |
||||||
880 | ); |
||||||
881 | } |
||||||
882 | |||||||
883 | /** |
||||||
884 | * Return the custom frontend routes |
||||||
885 | * |
||||||
886 | * @return array |
||||||
887 | */ |
||||||
888 | protected function customFrontendRoutes(): array |
||||||
889 | { |
||||||
890 | return [ |
||||||
891 | ]; |
||||||
892 | } |
||||||
893 | |||||||
894 | /** |
||||||
895 | * Install site event listeners for Control Panel requests only |
||||||
896 | */ |
||||||
897 | protected function installCpEventListeners() |
||||||
898 | { |
||||||
899 | // Load the frontend template containers |
||||||
900 | self::$plugin->frontendTemplates->loadFrontendTemplateContainers(); |
||||||
901 | // Handler: UrlManager::EVENT_REGISTER_CP_URL_RULES |
||||||
902 | Event::on( |
||||||
903 | UrlManager::class, |
||||||
904 | UrlManager::EVENT_REGISTER_CP_URL_RULES, |
||||||
905 | function(RegisterUrlRulesEvent $event) { |
||||||
906 | Craft::debug( |
||||||
907 | 'UrlManager::EVENT_REGISTER_CP_URL_RULES', |
||||||
908 | __METHOD__ |
||||||
909 | ); |
||||||
910 | // Register our Control Panel routes |
||||||
911 | $event->rules = array_merge( |
||||||
912 | $event->rules, |
||||||
913 | $this->customAdminCpRoutes() |
||||||
914 | ); |
||||||
915 | } |
||||||
916 | ); |
||||||
917 | // Handler: UserPermissions::EVENT_REGISTER_PERMISSIONS |
||||||
918 | Event::on( |
||||||
919 | UserPermissions::class, |
||||||
920 | UserPermissions::EVENT_REGISTER_PERMISSIONS, |
||||||
921 | function(RegisterUserPermissionsEvent $event) { |
||||||
922 | Craft::debug( |
||||||
923 | 'UserPermissions::EVENT_REGISTER_PERMISSIONS', |
||||||
924 | __METHOD__ |
||||||
925 | ); |
||||||
926 | // Register our custom permissions |
||||||
927 | $event->permissions[Craft::t('seomatic', 'SEOmatic')] = $this->customAdminCpPermissions(); |
||||||
928 | } |
||||||
929 | ); |
||||||
930 | // Handler: AutocompleteService::EVENT_REGISTER_CODEEDITOR_AUTOCOMPLETES |
||||||
931 | Event::on(AutocompleteService::class, AutocompleteService::EVENT_REGISTER_CODEEDITOR_AUTOCOMPLETES, |
||||||
932 | function(RegisterCodeEditorAutocompletesEvent $event) { |
||||||
933 | if ($event->fieldType === self::SEOMATIC_EXPRESSION_FIELD_TYPE) { |
||||||
934 | $event->types[] = EnvironmentVariableAutocomplete::class; |
||||||
935 | } |
||||||
936 | if ($event->fieldType === self::SEOMATIC_TRACKING_FIELD_TYPE) { |
||||||
937 | $event->types[] = TrackingVarsAutocomplete::class; |
||||||
938 | } |
||||||
939 | } |
||||||
940 | ); |
||||||
941 | // Handler: TwigTemplateValidator::EVENT_REGISTER_TWIG_VALIDATOR_VARIABLES |
||||||
942 | Event::on(TwigTemplateValidator::class, |
||||||
943 | TwigTemplateValidator::EVENT_REGISTER_TWIG_VALIDATOR_VARIABLES, |
||||||
944 | function(RegisterTwigValidatorVariablesEvent $event) { |
||||||
945 | if (Seomatic::$seomaticVariable === null) { |
||||||
946 | Seomatic::$seomaticVariable = new SeomaticVariable(); |
||||||
947 | Seomatic::$plugin->metaContainers->loadGlobalMetaContainers(); |
||||||
948 | Seomatic::$seomaticVariable->init(); |
||||||
949 | } |
||||||
950 | $event->variables['seomatic'] = Seomatic::$seomaticVariable; |
||||||
951 | } |
||||||
952 | ); |
||||||
953 | } |
||||||
954 | |||||||
955 | /** |
||||||
956 | * Return the custom Control Panel routes |
||||||
957 | * |
||||||
958 | * @return array |
||||||
959 | */ |
||||||
960 | protected function customAdminCpRoutes(): array |
||||||
961 | { |
||||||
962 | return [ |
||||||
963 | 'seomatic' => |
||||||
964 | '', |
||||||
965 | 'seomatic/dashboard' => |
||||||
966 | 'seomatic/settings/dashboard', |
||||||
967 | 'seomatic/dashboard/<siteHandle:{handle}>' => |
||||||
968 | 'seomatic/settings/dashboard', |
||||||
969 | |||||||
970 | 'seomatic/global' => [ |
||||||
971 | 'route' => 'seomatic/settings/global', |
||||||
972 | 'defaults' => ['subSection' => 'general'], |
||||||
973 | ], |
||||||
974 | 'seomatic/global/<subSection:{handle}>' => |
||||||
975 | 'seomatic/settings/global', |
||||||
976 | 'seomatic/global/<subSection:{handle}>/<siteHandle:{handle}>' => |
||||||
977 | 'seomatic/settings/global', |
||||||
978 | |||||||
979 | 'seomatic/content' => |
||||||
980 | 'seomatic/settings/content', |
||||||
981 | 'seomatic/content/<siteHandle:{handle}>' => |
||||||
982 | 'seomatic/settings/content', |
||||||
983 | |||||||
984 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{handle}>' => |
||||||
985 | 'seomatic/settings/edit-content', |
||||||
986 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{handle}>/<siteHandle:{handle}>' => |
||||||
987 | 'seomatic/settings/edit-content', |
||||||
988 | |||||||
989 | // Seemingly duplicate route needed to handle Solspace Calendar, which allows characters like -'s |
||||||
990 | // in their handles |
||||||
991 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{slug}>' => |
||||||
992 | 'seomatic/settings/edit-content', |
||||||
993 | 'seomatic/edit-content/<subSection:{handle}>/<sourceBundleType:{handle}>/<sourceHandle:{slug}>/<siteHandle:{handle}>' => |
||||||
994 | 'seomatic/settings/edit-content', |
||||||
995 | |||||||
996 | 'seomatic/site' => [ |
||||||
997 | 'route' => 'seomatic/settings/site', |
||||||
998 | 'defaults' => ['subSection' => 'identity'], |
||||||
999 | ], |
||||||
1000 | 'seomatic/site/<subSection:{handle}>' => |
||||||
1001 | 'seomatic/settings/site', |
||||||
1002 | 'seomatic/site/<subSection:{handle}>/<siteHandle:{handle}>' => |
||||||
1003 | 'seomatic/settings/site', |
||||||
1004 | |||||||
1005 | 'seomatic/tracking' => [ |
||||||
1006 | 'route' => 'seomatic/settings/tracking', |
||||||
1007 | 'defaults' => ['subSection' => 'googleAnalytics'], |
||||||
1008 | ], |
||||||
1009 | 'seomatic/tracking/<subSection:{handle}>' => |
||||||
1010 | 'seomatic/settings/tracking', |
||||||
1011 | 'seomatic/tracking/<subSection:{handle}>/<siteHandle:{handle}>' => |
||||||
1012 | 'seomatic/settings/tracking', |
||||||
1013 | |||||||
1014 | 'seomatic/plugin' => |
||||||
1015 | 'seomatic/settings/plugin', |
||||||
1016 | ]; |
||||||
1017 | } |
||||||
1018 | |||||||
1019 | /** |
||||||
1020 | * Returns the custom Control Panel user permissions. |
||||||
1021 | * |
||||||
1022 | * @return array |
||||||
1023 | */ |
||||||
1024 | protected function customAdminCpPermissions(): array |
||||||
1025 | { |
||||||
1026 | // The script meta containers for the global meta bundle |
||||||
1027 | try { |
||||||
1028 | $currentSiteId = Craft::$app->getSites()->getCurrentSite()->id ?? 1; |
||||||
1029 | } catch (SiteNotFoundException $e) { |
||||||
1030 | $currentSiteId = 1; |
||||||
1031 | } |
||||||
1032 | // Dynamic permissions for the scripts |
||||||
1033 | $metaBundle = self::$plugin->metaBundles->getGlobalMetaBundle($currentSiteId); |
||||||
1034 | $scriptsPerms = []; |
||||||
1035 | if ($metaBundle !== null) { |
||||||
1036 | $scripts = self::$plugin->metaBundles->getContainerDataFromBundle( |
||||||
1037 | $metaBundle, |
||||||
1038 | MetaScriptContainer::CONTAINER_TYPE |
||||||
1039 | ); |
||||||
1040 | foreach ($scripts as $scriptHandle => $scriptData) { |
||||||
1041 | $scriptsPerms["seomatic:tracking-scripts:${scriptHandle}"] = [ |
||||||
1042 | 'label' => Craft::t('seomatic', $scriptData->name), |
||||||
1043 | ]; |
||||||
1044 | } |
||||||
1045 | } |
||||||
1046 | |||||||
1047 | return [ |
||||||
1048 | 'seomatic:dashboard' => [ |
||||||
1049 | 'label' => Craft::t('seomatic', 'Dashboard'), |
||||||
1050 | ], |
||||||
1051 | 'seomatic:global-meta' => [ |
||||||
1052 | 'label' => Craft::t('seomatic', 'Edit Global Meta'), |
||||||
1053 | 'nested' => [ |
||||||
1054 | 'seomatic:global-meta:general' => [ |
||||||
1055 | 'label' => Craft::t('seomatic', 'General'), |
||||||
1056 | ], |
||||||
1057 | 'seomatic:global-meta:twitter' => [ |
||||||
1058 | 'label' => Craft::t('seomatic', 'Twitter'), |
||||||
1059 | ], |
||||||
1060 | 'seomatic:global-meta:facebook' => [ |
||||||
1061 | 'label' => Craft::t('seomatic', 'Facebook'), |
||||||
1062 | ], |
||||||
1063 | 'seomatic:global-meta:robots' => [ |
||||||
1064 | 'label' => Craft::t('seomatic', 'Robots'), |
||||||
1065 | ], |
||||||
1066 | 'seomatic:global-meta:humans' => [ |
||||||
1067 | 'label' => Craft::t('seomatic', 'Humans'), |
||||||
1068 | ], |
||||||
1069 | 'seomatic:global-meta:ads' => [ |
||||||
1070 | 'label' => Craft::t('seomatic', 'Ads'), |
||||||
1071 | ], |
||||||
1072 | 'seomatic:global-meta:security' => [ |
||||||
1073 | 'label' => Craft::t('seomatic', 'Security'), |
||||||
1074 | ], |
||||||
1075 | ], |
||||||
1076 | ], |
||||||
1077 | 'seomatic:content-meta' => [ |
||||||
1078 | 'label' => Craft::t('seomatic', 'Edit Content SEO'), |
||||||
1079 | 'nested' => [ |
||||||
1080 | 'seomatic:content-meta:general' => [ |
||||||
1081 | 'label' => Craft::t('seomatic', 'General'), |
||||||
1082 | ], |
||||||
1083 | 'seomatic:content-meta:twitter' => [ |
||||||
1084 | 'label' => Craft::t('seomatic', 'Twitter'), |
||||||
1085 | ], |
||||||
1086 | 'seomatic:content-meta:facebook' => [ |
||||||
1087 | 'label' => Craft::t('seomatic', 'Facebook'), |
||||||
1088 | ], |
||||||
1089 | 'seomatic:content-meta:sitemap' => [ |
||||||
1090 | 'label' => Craft::t('seomatic', 'Sitemap'), |
||||||
1091 | ], |
||||||
1092 | ], |
||||||
1093 | ], |
||||||
1094 | 'seomatic:site-settings' => [ |
||||||
1095 | 'label' => Craft::t('seomatic', 'Edit Site Settings'), |
||||||
1096 | 'nested' => [ |
||||||
1097 | 'seomatic:site-settings:identity' => [ |
||||||
1098 | 'label' => Craft::t('seomatic', 'Identity'), |
||||||
1099 | ], |
||||||
1100 | 'seomatic:site-settings:creator' => [ |
||||||
1101 | 'label' => Craft::t('seomatic', 'Creator'), |
||||||
1102 | ], |
||||||
1103 | 'seomatic:site-settings:social' => [ |
||||||
1104 | 'label' => Craft::t('seomatic', 'Social Media'), |
||||||
1105 | ], |
||||||
1106 | 'seomatic:site-settings:sitemap' => [ |
||||||
1107 | 'label' => Craft::t('seomatic', 'Sitemap'), |
||||||
1108 | ], |
||||||
1109 | 'seomatic:site-settings:miscellaneous' => [ |
||||||
1110 | 'label' => Craft::t('seomatic', 'Miscellaneous'), |
||||||
1111 | ], |
||||||
1112 | ], |
||||||
1113 | ], |
||||||
1114 | 'seomatic:tracking-scripts' => [ |
||||||
1115 | 'label' => Craft::t('seomatic', 'Edit Tracking Scripts'), |
||||||
1116 | 'nested' => $scriptsPerms, |
||||||
1117 | ], |
||||||
1118 | 'seomatic:plugin-settings' => [ |
||||||
1119 | 'label' => Craft::t('seomatic', 'Edit Plugin Settings'), |
||||||
1120 | ], |
||||||
1121 | ]; |
||||||
1122 | } |
||||||
1123 | |||||||
1124 | /** |
||||||
1125 | * Returns the custom Control Panel cache options. |
||||||
1126 | * |
||||||
1127 | * @return array |
||||||
1128 | */ |
||||||
1129 | protected function customAdminCpCacheOptions(): array |
||||||
1130 | { |
||||||
1131 | return [ |
||||||
1132 | // Frontend template caches |
||||||
1133 | [ |
||||||
1134 | 'key' => 'seomatic-frontendtemplate-caches', |
||||||
1135 | 'label' => Craft::t('seomatic', 'SEOmatic frontend template caches'), |
||||||
1136 | 'action' => [self::$plugin->frontendTemplates, 'invalidateCaches'], |
||||||
1137 | ], |
||||||
1138 | // Meta bundle caches |
||||||
1139 | [ |
||||||
1140 | 'key' => 'seomatic-metabundle-caches', |
||||||
1141 | 'label' => Craft::t('seomatic', 'SEOmatic metadata caches'), |
||||||
1142 | 'action' => [self::$plugin->metaContainers, 'invalidateCaches'], |
||||||
1143 | ], |
||||||
1144 | // Sitemap caches |
||||||
1145 | [ |
||||||
1146 | 'key' => 'seomatic-sitemap-caches', |
||||||
1147 | 'label' => Craft::t('seomatic', 'SEOmatic sitemap caches'), |
||||||
1148 | 'action' => [self::$plugin->sitemaps, 'invalidateCaches'], |
||||||
1149 | ], |
||||||
1150 | // Schema caches |
||||||
1151 | [ |
||||||
1152 | 'key' => 'seomatic-schema-caches', |
||||||
1153 | 'label' => Craft::t('seomatic', 'SEOmatic schema caches'), |
||||||
1154 | 'action' => [SchemaHelper::class, 'invalidateCaches'], |
||||||
1155 | ], |
||||||
1156 | ]; |
||||||
1157 | } |
||||||
1158 | |||||||
1159 | /** |
||||||
1160 | * @inheritdoc |
||||||
1161 | */ |
||||||
1162 | protected function createSettingsModel() |
||||||
1163 | { |
||||||
1164 | return new Settings(); |
||||||
1165 | } |
||||||
1166 | } |
||||||
1167 |