nystudio107 /
craft-instantanalytics-ga4
| 1 | <?php |
||||||
| 2 | /** |
||||||
| 3 | * Instant Analytics plugin for Craft CMS |
||||||
| 4 | * |
||||||
| 5 | * @author nystudio107 |
||||||
|
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
|
|||||||
| 6 | * @copyright Copyright (c) 2017 nystudio107 |
||||||
|
0 ignored issues
–
show
|
|||||||
| 7 | * @link http://nystudio107.com |
||||||
|
0 ignored issues
–
show
|
|||||||
| 8 | * @package InstantAnalytics |
||||||
|
0 ignored issues
–
show
|
|||||||
| 9 | * @since 1.0.0 |
||||||
| 10 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 11 | |||||||
| 12 | namespace nystudio107\instantanalyticsGa4\ga4; |
||||||
| 13 | |||||||
| 14 | use Br33f\Ga4\MeasurementProtocol\Dto\Event\AbstractEvent; |
||||||
| 15 | use Br33f\Ga4\MeasurementProtocol\Dto\Request\BaseRequest; |
||||||
| 16 | use Br33f\Ga4\MeasurementProtocol\Exception\HydrationException; |
||||||
| 17 | use Br33f\Ga4\MeasurementProtocol\Exception\ValidationException; |
||||||
| 18 | use Br33f\Ga4\MeasurementProtocol\HttpClient; |
||||||
| 19 | use Craft; |
||||||
| 20 | use craft\commerce\elements\Order; |
||||||
| 21 | use craft\commerce\elements\Product; |
||||||
| 22 | use craft\commerce\elements\Variant; |
||||||
| 23 | use craft\errors\MissingComponentException; |
||||||
| 24 | use craft\helpers\App; |
||||||
| 25 | use nystudio107\instantanalyticsGa4\helpers\Analytics as AnalyticsHelper; |
||||||
| 26 | use nystudio107\instantanalyticsGa4\InstantAnalytics; |
||||||
| 27 | use nystudio107\seomatic\Seomatic; |
||||||
| 28 | use yii\base\InvalidConfigException; |
||||||
| 29 | |||||||
| 30 | /** |
||||||
|
0 ignored issues
–
show
|
|||||||
| 31 | * @author nystudio107 |
||||||
|
0 ignored issues
–
show
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
|
|||||||
| 32 | * @package InstantAnalytics |
||||||
|
0 ignored issues
–
show
|
|||||||
| 33 | * @since 1.2.0 |
||||||
|
0 ignored issues
–
show
|
|||||||
| 34 | * |
||||||
| 35 | * @method Analytics setAllowGoogleSignals(string $value) |
||||||
| 36 | * @method Analytics setAllowAdPersonalizationSignals(string $value) |
||||||
| 37 | * @method Analytics setCampaignContent(string $value) |
||||||
| 38 | * @method Analytics setCampaignId(string $value) |
||||||
| 39 | * @method Analytics setCampaignMedium(string $value) |
||||||
| 40 | * @method Analytics setCampaignName(string $value) |
||||||
| 41 | * @method Analytics setCampaignSource(string $value) |
||||||
| 42 | * @method Analytics setCampaignTerm(string $value) |
||||||
| 43 | * @method Analytics setCampaign(string $value) |
||||||
| 44 | * @method Analytics setClientId(string $value) |
||||||
| 45 | * @method Analytics setContentGroup(string $value) |
||||||
| 46 | * @method Analytics setCookieDomain(string $value) |
||||||
| 47 | * @method Analytics setCookieExpires(string $value) |
||||||
| 48 | * @method Analytics setCookieFlags(string $value) |
||||||
| 49 | * @method Analytics setCookiePath(string $value) |
||||||
| 50 | * @method Analytics setCookiePrefix(string $value) |
||||||
| 51 | * @method Analytics setCookieUpdate(string $value) |
||||||
| 52 | * @method Analytics setLanguage(string $value) |
||||||
| 53 | * @method Analytics setPageLocation(string $value) |
||||||
| 54 | * @method Analytics setPageReferrer(string $value) |
||||||
| 55 | * @method Analytics setPageTitle(string $value) |
||||||
| 56 | * @method Analytics setSendPageView(string $value) |
||||||
| 57 | * @method Analytics setScreenResolution(string $value) |
||||||
| 58 | * @method Analytics setUserId(string $value) |
||||||
| 59 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 60 | class Analytics |
||||||
| 61 | { |
||||||
| 62 | /** |
||||||
|
0 ignored issues
–
show
|
|||||||
| 63 | * @var BaseRequest|null |
||||||
| 64 | */ |
||||||
| 65 | private ?BaseRequest $_request = null; |
||||||
| 66 | |||||||
| 67 | /** |
||||||
|
0 ignored issues
–
show
|
|||||||
| 68 | * @var Service|null|false |
||||||
| 69 | */ |
||||||
| 70 | private mixed $_service = null; |
||||||
| 71 | |||||||
| 72 | /** |
||||||
|
0 ignored issues
–
show
|
|||||||
| 73 | * @var string|null |
||||||
| 74 | */ |
||||||
| 75 | private ?string $_affiliation = null; |
||||||
| 76 | |||||||
| 77 | private ?bool $_shouldSendAnalytics = null; |
||||||
| 78 | |||||||
| 79 | private ?string $_sessionString = null; |
||||||
| 80 | |||||||
| 81 | private array $eventList = []; |
||||||
|
0 ignored issues
–
show
|
|||||||
| 82 | |||||||
| 83 | /** |
||||||
| 84 | * Component factory for creating events. |
||||||
| 85 | * |
||||||
| 86 | * @return ComponentFactory |
||||||
| 87 | */ |
||||||
| 88 | public function create(): ComponentFactory |
||||||
| 89 | { |
||||||
| 90 | return new ComponentFactory(); |
||||||
| 91 | } |
||||||
| 92 | |||||||
| 93 | /** |
||||||
| 94 | * Add an event to be sent to Google |
||||||
| 95 | * |
||||||
| 96 | * @param AbstractEvent $event |
||||||
|
0 ignored issues
–
show
|
|||||||
| 97 | * @return void |
||||||
|
0 ignored issues
–
show
|
|||||||
| 98 | */ |
||||||
| 99 | public function addEvent(AbstractEvent $event): void |
||||||
| 100 | { |
||||||
| 101 | if ($this->_sessionString === null) { |
||||||
| 102 | $this->_sessionString = AnalyticsHelper::getSessionString(); |
||||||
| 103 | } |
||||||
| 104 | |||||||
| 105 | if (str_contains($this->_sessionString, '.')) { |
||||||
|
0 ignored issues
–
show
It seems like
$this->_sessionString can also be of type null; however, parameter $haystack of str_contains() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 106 | [$sessionId, $sessionNumber] = explode('.', $this->_sessionString); |
||||||
|
0 ignored issues
–
show
It seems like
$this->_sessionString can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 107 | $event->setParamValue('sessionId', $sessionId); |
||||||
| 108 | $event->setParamValue('sessionNumber', $sessionNumber); |
||||||
| 109 | } |
||||||
| 110 | |||||||
| 111 | $this->eventList[] = $event; |
||||||
| 112 | } |
||||||
| 113 | |||||||
| 114 | /** |
||||||
| 115 | * Send the events collected so far. |
||||||
| 116 | * |
||||||
| 117 | * @return ?array |
||||||
| 118 | * @throws HydrationException |
||||||
| 119 | * @throws ValidationException |
||||||
| 120 | */ |
||||||
| 121 | public function sendCollectedEvents(): ?array |
||||||
| 122 | { |
||||||
| 123 | if ($this->_shouldSendAnalytics === null) { |
||||||
| 124 | $this->_shouldSendAnalytics = AnalyticsHelper::shouldSendAnalytics(); |
||||||
| 125 | } |
||||||
| 126 | |||||||
| 127 | if (!$this->_shouldSendAnalytics) { |
||||||
|
0 ignored issues
–
show
The expression
$this->_shouldSendAnalytics of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.
If an expression can have both $a = canBeFalseAndNull();
// Instead of
if ( ! $a) { }
// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
|
|||||||
| 128 | return null; |
||||||
| 129 | } |
||||||
| 130 | |||||||
| 131 | $service = $this->service(); |
||||||
| 132 | |||||||
| 133 | if (!$service) { |
||||||
| 134 | return null; |
||||||
| 135 | } |
||||||
| 136 | |||||||
| 137 | $request = $this->request(); |
||||||
| 138 | $eventCount = count($this->eventList); |
||||||
| 139 | |||||||
| 140 | if (!InstantAnalytics::$settings->sendAnalyticsData) { |
||||||
| 141 | InstantAnalytics::$plugin->logAnalyticsEvent( |
||||||
|
0 ignored issues
–
show
The method
logAnalyticsEvent() 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. Loading history...
|
|||||||
| 142 | 'Analytics not enabled - skipped sending {count} events', |
||||||
| 143 | ['count' => $eventCount], |
||||||
| 144 | __METHOD__ |
||||||
| 145 | ); |
||||||
| 146 | |||||||
| 147 | return null; |
||||||
| 148 | } |
||||||
| 149 | |||||||
| 150 | if ($eventCount === 0) { |
||||||
| 151 | InstantAnalytics::$plugin->logAnalyticsEvent( |
||||||
| 152 | 'No events collected to send', |
||||||
| 153 | [], |
||||||
| 154 | __METHOD__ |
||||||
| 155 | ); |
||||||
| 156 | |||||||
| 157 | return null; |
||||||
| 158 | } |
||||||
| 159 | |||||||
| 160 | InstantAnalytics::$plugin->logAnalyticsEvent( |
||||||
| 161 | 'Sending {count} analytics events', |
||||||
| 162 | ['count' => $eventCount], |
||||||
| 163 | __METHOD__ |
||||||
| 164 | ); |
||||||
| 165 | |||||||
| 166 | // Batch into groups of 25 |
||||||
| 167 | $responses = []; |
||||||
| 168 | |||||||
| 169 | foreach (array_chunk($this->eventList, 25) as $chunk) { |
||||||
| 170 | $request->getEvents()->setEventList([]); |
||||||
| 171 | |||||||
| 172 | /** @var AbstractEvent $event */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 173 | foreach ($chunk as $event) { |
||||||
| 174 | $request->addEvent($event); |
||||||
| 175 | } |
||||||
| 176 | |||||||
| 177 | $responses[] = $service->send($request); |
||||||
| 178 | } |
||||||
| 179 | |||||||
| 180 | |||||||
| 181 | return $responses; |
||||||
| 182 | } |
||||||
| 183 | |||||||
| 184 | public function getAffiliation(): ?string |
||||||
|
0 ignored issues
–
show
|
|||||||
| 185 | { |
||||||
| 186 | return $this->_affiliation; |
||||||
| 187 | } |
||||||
| 188 | |||||||
| 189 | /** |
||||||
| 190 | * Set affiliation for all the events that incorporate Commerce Product info for the remaining duration of request. |
||||||
| 191 | * |
||||||
| 192 | * @param string $affiliation |
||||||
|
0 ignored issues
–
show
|
|||||||
| 193 | * @return $this |
||||||
|
0 ignored issues
–
show
|
|||||||
| 194 | */ |
||||||
| 195 | public function setAffiliation(string $affiliation): self |
||||||
| 196 | { |
||||||
| 197 | $this->_affiliation = $affiliation; |
||||||
| 198 | return $this; |
||||||
| 199 | } |
||||||
| 200 | |||||||
| 201 | /** |
||||||
| 202 | * Add a commerce item list impression. |
||||||
| 203 | * |
||||||
| 204 | * @param Product|Variant $productVariant |
||||||
|
0 ignored issues
–
show
|
|||||||
| 205 | * @param int $index |
||||||
|
0 ignored issues
–
show
|
|||||||
| 206 | * @param string $listName |
||||||
|
0 ignored issues
–
show
|
|||||||
| 207 | * @throws InvalidConfigException |
||||||
|
0 ignored issues
–
show
|
|||||||
| 208 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 209 | public function addCommerceProductImpression(Product|Variant $productVariant, int $index = 0, string $listName = 'default') |
||||||
| 210 | { |
||||||
| 211 | InstantAnalytics::$plugin->commerce->addCommerceProductImpression($productVariant); |
||||||
| 212 | } |
||||||
| 213 | |||||||
| 214 | /** |
||||||
| 215 | * Begin checkout. |
||||||
| 216 | * |
||||||
| 217 | * @param Order $cart |
||||||
|
0 ignored issues
–
show
|
|||||||
| 218 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 219 | public function beginCheckout(Order $cart) |
||||||
| 220 | { |
||||||
| 221 | InstantAnalytics::$plugin->commerce->triggerBeginCheckoutEvent($cart); |
||||||
| 222 | } |
||||||
| 223 | |||||||
| 224 | /** |
||||||
| 225 | * Add a commerce item list impression. |
||||||
| 226 | * |
||||||
| 227 | * @param Product|Variant $productVariant |
||||||
|
0 ignored issues
–
show
|
|||||||
| 228 | * @param int $index |
||||||
|
0 ignored issues
–
show
|
|||||||
| 229 | * @param string $listName |
||||||
|
0 ignored issues
–
show
|
|||||||
| 230 | * @throws InvalidConfigException |
||||||
|
0 ignored issues
–
show
|
|||||||
| 231 | * @deprecated `Analytics::addCommerceProductDetailView()` is deprecated. Use `Analytics::addCommerceProductImpression()` instead. |
||||||
|
0 ignored issues
–
show
|
|||||||
| 232 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 233 | public function addCommerceProductDetailView(Product|Variant $productVariant, int $index = 0, string $listName = 'default') |
||||||
| 234 | { |
||||||
| 235 | Craft::$app->getDeprecator()->log('Analytics::addCommerceProductDetailView()', '`Analytics::addCommerceProductDetailView()` is deprecated. Use `Analytics::addCommerceProductImpression()` instead.'); |
||||||
| 236 | $this->addCommerceProductImpression($productVariant); |
||||||
| 237 | } |
||||||
| 238 | |||||||
| 239 | /** |
||||||
| 240 | * Add a commerce product list impression. |
||||||
| 241 | * |
||||||
| 242 | * @param array $products |
||||||
|
0 ignored issues
–
show
|
|||||||
| 243 | * @param $listName |
||||||
|
0 ignored issues
–
show
|
|||||||
| 244 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 245 | public function addCommerceProductListImpression(array $products, string $listName = 'default') |
||||||
| 246 | { |
||||||
| 247 | InstantAnalytics::$plugin->commerce->addCommerceProductListImpression($products, $listName); |
||||||
| 248 | } |
||||||
| 249 | |||||||
| 250 | /** |
||||||
| 251 | * Set the measurement id. |
||||||
| 252 | * |
||||||
| 253 | * @param string $measurementId |
||||||
|
0 ignored issues
–
show
|
|||||||
| 254 | * @return $this |
||||||
|
0 ignored issues
–
show
|
|||||||
| 255 | */ |
||||||
| 256 | public function setMeasurementId(string $measurementId): self |
||||||
| 257 | { |
||||||
| 258 | $this->service()?->setMeasurementId($measurementId); |
||||||
| 259 | return $this; |
||||||
| 260 | } |
||||||
| 261 | |||||||
| 262 | /** |
||||||
| 263 | * Set the API secret. |
||||||
| 264 | * |
||||||
| 265 | * @param string $apiSecret |
||||||
|
0 ignored issues
–
show
|
|||||||
| 266 | * @return $this |
||||||
|
0 ignored issues
–
show
|
|||||||
| 267 | */ |
||||||
| 268 | public function setApiSecret(string $apiSecret): self |
||||||
| 269 | { |
||||||
| 270 | $this->service()?->setApiSecret($apiSecret); |
||||||
| 271 | return $this; |
||||||
| 272 | } |
||||||
| 273 | |||||||
| 274 | public function __call(string $methodName, array $arguments): ?self |
||||||
|
0 ignored issues
–
show
|
|||||||
| 275 | { |
||||||
| 276 | $knownProperties = [ |
||||||
| 277 | 'allowGoogleSignals' => 'allow_google_signals', |
||||||
| 278 | 'allowAdPersonalizationSignals' => 'allow_ad_personalization_signals', |
||||||
| 279 | 'campaignContent' => 'campaign_content', |
||||||
| 280 | 'campaignId' => 'campaign_id', |
||||||
| 281 | 'campaignMedium' => 'campaign_medium', |
||||||
| 282 | 'campaignName' => 'campaign_name', |
||||||
| 283 | 'campaignSource' => 'campaign_source', |
||||||
| 284 | 'campaignTerm' => 'campaign_term', |
||||||
| 285 | 'campaign' => 'campaign', |
||||||
| 286 | 'clientId' => 'client_id', |
||||||
| 287 | 'contentGroup' => 'content_group', |
||||||
| 288 | 'cookieDomain' => 'cookie_domain', |
||||||
| 289 | 'cookieExpires' => 'cookie_expires', |
||||||
| 290 | 'cookieFlags' => 'cookie_flags', |
||||||
| 291 | 'cookiePath' => 'cookie_path', |
||||||
| 292 | 'cookiePrefix' => 'cookie_prefix', |
||||||
| 293 | 'cookieUpdate' => 'cookie_update', |
||||||
| 294 | 'language' => 'language', |
||||||
| 295 | 'pageLocation' => 'page_location', |
||||||
| 296 | 'pageReferrer' => 'page_referrer', |
||||||
| 297 | 'pageTitle' => 'page_title', |
||||||
| 298 | 'sendPageView' => 'send_page_view', |
||||||
| 299 | 'screenResolution' => 'screen_resolution', |
||||||
| 300 | 'userId' => 'user_id', |
||||||
| 301 | ]; |
||||||
| 302 | |||||||
| 303 | if (str_starts_with($methodName, 'set')) { |
||||||
| 304 | $methodName = lcfirst(substr($methodName, 3)); |
||||||
| 305 | |||||||
| 306 | $service = $this->service(); |
||||||
| 307 | if ($service && !empty($knownProperties[$methodName])) { |
||||||
| 308 | $service->setAdditionalQueryParam($knownProperties[$methodName], $arguments[0]); |
||||||
| 309 | |||||||
| 310 | return $this; |
||||||
| 311 | } |
||||||
| 312 | } |
||||||
| 313 | |||||||
| 314 | return null; |
||||||
| 315 | } |
||||||
| 316 | |||||||
| 317 | public function request(): BaseRequest |
||||||
|
0 ignored issues
–
show
|
|||||||
| 318 | { |
||||||
| 319 | if ($this->_request === null) { |
||||||
| 320 | $this->_request = new BaseRequest(); |
||||||
| 321 | |||||||
| 322 | $this->_request->setClientId(AnalyticsHelper::getClientId()); |
||||||
|
0 ignored issues
–
show
The method
setClientId() 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. Loading history...
|
|||||||
| 323 | |||||||
| 324 | if (InstantAnalytics::$settings->sendUserId) { |
||||||
| 325 | $userId = AnalyticsHelper::getUserId(); |
||||||
| 326 | |||||||
| 327 | if ($userId) { |
||||||
| 328 | $this->request()->setUserId($userId); |
||||||
| 329 | } |
||||||
| 330 | } |
||||||
| 331 | } |
||||||
| 332 | |||||||
| 333 | |||||||
| 334 | return $this->_request; |
||||||
|
0 ignored issues
–
show
|
|||||||
| 335 | } |
||||||
| 336 | |||||||
| 337 | /** |
||||||
| 338 | * Init the service used to send events |
||||||
| 339 | */ |
||||||
|
0 ignored issues
–
show
|
|||||||
| 340 | public function init(): void |
||||||
| 341 | { |
||||||
| 342 | $this->service(); |
||||||
| 343 | $this->request(); |
||||||
| 344 | } |
||||||
| 345 | |||||||
| 346 | protected function service(): ?Service |
||||||
|
0 ignored issues
–
show
|
|||||||
| 347 | { |
||||||
| 348 | if ($this->_service === null) { |
||||||
| 349 | $settings = InstantAnalytics::$settings; |
||||||
| 350 | $apiSecret = App::parseEnv($settings->googleAnalyticsMeasurementApiSecret); |
||||||
| 351 | $measurementId = App::parseEnv($settings->googleAnalyticsMeasurementId); |
||||||
| 352 | |||||||
| 353 | if (empty($apiSecret) || empty($measurementId)) { |
||||||
| 354 | InstantAnalytics::$plugin->logAnalyticsEvent( |
||||||
| 355 | 'API secret or measurement ID not set up for Instant Analytics', |
||||||
| 356 | [], |
||||||
| 357 | __METHOD__ |
||||||
| 358 | ); |
||||||
| 359 | $this->_service = false; |
||||||
| 360 | |||||||
| 361 | return null; |
||||||
| 362 | } |
||||||
| 363 | $this->_service = new Service($apiSecret, $measurementId); |
||||||
| 364 | |||||||
| 365 | $ga4Client = new HttpClient(); |
||||||
| 366 | $ga4Client->setClient(Craft::createGuzzleClient()); |
||||||
| 367 | $this->_service->setHttpClient($ga4Client); |
||||||
| 368 | |||||||
| 369 | $request = Craft::$app->getRequest(); |
||||||
| 370 | try { |
||||||
| 371 | $session = Craft::$app->getSession(); |
||||||
| 372 | } catch (MissingComponentException $exception) { |
||||||
| 373 | $session = null; |
||||||
| 374 | } |
||||||
| 375 | |||||||
| 376 | $this->setPageReferrer($request->getReferrer()); |
||||||
| 377 | |||||||
| 378 | // Load any campaign values from session or request |
||||||
| 379 | $campaignParams = [ |
||||||
| 380 | 'utm_source' => 'CampaignSource', |
||||||
| 381 | 'utm_medium' => 'CampaignMedium', |
||||||
| 382 | 'utm_campaign' => 'CampaignName', |
||||||
| 383 | 'utm_content' => 'CampaignContent', |
||||||
| 384 | 'utm_term' => 'CampaignTerm', |
||||||
| 385 | ]; |
||||||
| 386 | |||||||
| 387 | // Load them up for GA4 |
||||||
| 388 | foreach ($campaignParams as $key => $method) { |
||||||
| 389 | $value = $request->getParam($key) ?? $session->get($key) ?? null; |
||||||
| 390 | $method = 'set' . $method; |
||||||
| 391 | |||||||
| 392 | $this->$method($value); |
||||||
| 393 | |||||||
| 394 | if ($session && $value) { |
||||||
| 395 | $session->set($key, $value); |
||||||
| 396 | } |
||||||
| 397 | } |
||||||
| 398 | |||||||
| 399 | // If SEOmatic is installed, set the affiliation as well |
||||||
| 400 | if (InstantAnalytics::$seomaticPlugin && Seomatic::$settings->renderEnabled && Seomatic::$plugin->metaContainers->metaSiteVars !== null) { |
||||||
| 401 | $siteName = Seomatic::$plugin->metaContainers->metaSiteVars->siteName; |
||||||
| 402 | $this->setAffiliation($siteName); |
||||||
| 403 | } |
||||||
| 404 | } |
||||||
| 405 | |||||||
| 406 | if ($this->_service === false) { |
||||||
| 407 | return null; |
||||||
| 408 | } |
||||||
| 409 | |||||||
| 410 | return $this->_service; |
||||||
| 411 | } |
||||||
| 412 | } |
||||||
| 413 |