├── CHANGELOG.md ├── phpstan.neon ├── ecs.php ├── src ├── translations │ └── en │ │ └── disqus.php ├── controllers │ └── DefaultController.php ├── config.php ├── icon.svg ├── templates │ ├── disqusEmbedTag.twig │ ├── disqusEmbedTagLazy.twig │ └── settings.twig ├── variables │ └── DisqusVariable.php ├── twigextensions │ └── DisqusTwigExtension.php ├── Disqus.php ├── models │ └── Settings.php └── services │ └── DisqusService.php ├── Makefile ├── LICENSE.md ├── README.md └── composer.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Disqus Changelog 2 | 3 | ## 5.0.0 - 2024.10.06 4 | ### Added 5 | * Initial Craft CMS 5 release 6 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - %currentWorkingDirectory%/vendor/craftcms/phpstan/phpstan.neon 3 | 4 | parameters: 5 | level: 5 6 | paths: 7 | - src 8 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | paths([ 8 | __DIR__ . '/src', 9 | __FILE__, 10 | ]); 11 | $ecsConfig->parallel(); 12 | $ecsConfig->sets([SetList::CRAFT_CMS_4]); 13 | }; 14 | -------------------------------------------------------------------------------- /src/translations/en/disqus.php: -------------------------------------------------------------------------------- 1 | '{name} plugin loaded', 19 | 'Public API Key missing' => 'Public API Key missing', 20 | ]; 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAJOR_VERSION?=5 2 | PLUGINDEV_PROJECT_DIR?=/Users/andrew/webdev/sites/plugindev/cms_v${MAJOR_VERSION}/ 3 | VENDOR?=nystudio107 4 | PROJECT_PATH?=${VENDOR}/$(shell basename $(CURDIR)) 5 | 6 | .PHONY: dev docs release 7 | 8 | # Start up the buildchain dev server 9 | dev: 10 | # Start up the docs dev server 11 | docs: 12 | ${MAKE} -C docs/ dev 13 | # Run code quality tools, tests, and build the buildchain & docs in preparation for a release 14 | release: --code-quality --code-tests --buildchain-clean-build --docs-clean-build 15 | # The internal targets used by the dev & release targets 16 | --buildchain-clean-build: 17 | --code-quality: 18 | ${MAKE} -C ${PLUGINDEV_PROJECT_DIR} -- ecs check vendor/${PROJECT_PATH}/src --fix 19 | ${MAKE} -C ${PLUGINDEV_PROJECT_DIR} -- phpstan analyze -c vendor/${PROJECT_PATH}/phpstan.neon 20 | --code-tests: 21 | --docs-clean-build: 22 | ${MAKE} -C docs/ clean 23 | ${MAKE} -C docs/ image-build 24 | ${MAKE} -C docs/ fix 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) nystudio107 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/controllers/DefaultController.php: -------------------------------------------------------------------------------- 1 | getUser()->logout(false); 39 | return $this->redirect($_SERVER['HTTP_REFERER']); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/config.php: -------------------------------------------------------------------------------- 1 | '', 28 | 'useSSO' => false, 29 | 'disqusPublicKey' => '', 30 | 'disqusSecretKey' => '', 31 | 'customLogin' => false, 32 | 'loginName' => '', 33 | 'loginButton' => '', 34 | 'loginIcon' => '', 35 | 'loginUrl' => '', 36 | 'loginLogoutUrl' => '', 37 | 'loginWidth' => '', 38 | 'loginHeight' => '', 39 | ]; 40 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://scrutinizer-ci.com/g/nystudio107/craft-disqus/?branch=v5) [](https://scrutinizer-ci.com/g/nystudio107/craft-disqus/?branch=v5) [](https://scrutinizer-ci.com/g/nystudio107/craft-disqus/build-status/v5) [](https://scrutinizer-ci.com/code-intelligence) 2 | 3 | # Disqus plugin for Craft CMS 5.x 4 | 5 | Integrates the Disqus commenting system into Craft CMS websites, including Single Sign On (SSO) and custom login/logout URLs 6 | 7 |  8 | 9 | ## Requirements 10 | 11 | This plugin requires Craft CMS 5.0.0 or later. 12 | 13 | ## Installation 14 | 15 | To install Disqus, follow these steps: 16 | 17 | 1. Install with Composer via `composer require nystudio107/craft-disqus` from your project directory 18 | 2. Install the plugin via `./craft install/plugin disqus` via the CLI -or- in the Craft Control Panel under Settings > Plugins 19 | 20 | You can also install Disqus via the **Plugin Store** in the Craft AdminCP. 21 | 22 | ## Documentation 23 | 24 | Click here -> [Disqus Documentation](https://nystudio107.com/plugins/disqus/documentation) 25 | 26 | Brought to you by [nystudio107](https://nystudio107.com) 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nystudio107/craft-disqus", 3 | "description": "Integrates the Disqus commenting system into Craft CMS websites, including Single Sign On (SSO) and custom login/logout URLs", 4 | "type": "craft-plugin", 5 | "version": "5.0.0", 6 | "keywords": [ 7 | "craft", 8 | "cms", 9 | "craftcms", 10 | "craft-plugin", 11 | "disqus" 12 | ], 13 | "support": { 14 | "docs": "https://nystudio107.com/docs/disqus/", 15 | "issues": "https://nystudio107.com/plugins/disqus/support", 16 | "source": "https://github.com/nystudio107/craft-disqus" 17 | }, 18 | "license": "MIT", 19 | "authors": [ 20 | { 21 | "name": "nystudio107", 22 | "homepage": "https://nystudio107.com" 23 | } 24 | ], 25 | "require": { 26 | "craftcms/cms": "^5.0.0" 27 | }, 28 | "require-dev": { 29 | "craftcms/ecs": "dev-main", 30 | "craftcms/phpstan": "dev-main", 31 | "craftcms/rector": "dev-main" 32 | }, 33 | "scripts": { 34 | "phpstan": "phpstan --ansi --memory-limit=1G", 35 | "check-cs": "ecs check --ansi", 36 | "fix-cs": "ecs check --fix --ansi" 37 | }, 38 | "config": { 39 | "allow-plugins": { 40 | "craftcms/plugin-installer": true, 41 | "yiisoft/yii2-composer": true 42 | }, 43 | "optimize-autoloader": true, 44 | "sort-packages": true 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "nystudio107\\disqus\\": "src/" 49 | } 50 | }, 51 | "extra": { 52 | "class": "nystudio107\\disqus\\Disqus", 53 | "handle": "disqus", 54 | "name": "Disqus" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/templates/disqusEmbedTag.twig: -------------------------------------------------------------------------------- 1 |
2 | 47 | 49 | -------------------------------------------------------------------------------- /src/variables/DisqusVariable.php: -------------------------------------------------------------------------------- 1 | disqusService->outputEmbedTag( 46 | $disqusIdentifier, 47 | $disqusTitle, 48 | $disqusUrl, 49 | $disqusCategoryId, 50 | $disqusLanguage, 51 | $scriptAttributes 52 | ); 53 | } 54 | 55 | /** 56 | * @param string $disqusIdentifier 57 | * 58 | * @return int 59 | */ 60 | public function disqusCount( 61 | string $disqusIdentifier = "", 62 | ): int { 63 | return Disqus::$plugin->disqusService->getCommentsCount( 64 | $disqusIdentifier 65 | ); 66 | } 67 | 68 | /** 69 | * Return whether we are running Craft 3.1 or later 70 | * 71 | * @return bool 72 | */ 73 | public function craft31(): bool 74 | { 75 | return true; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/twigextensions/DisqusTwigExtension.php: -------------------------------------------------------------------------------- 1 | disqusService->outputEmbedTag( 79 | $disqusIdentifier, 80 | $disqusTitle, 81 | $disqusUrl, 82 | $disqusCategoryId, 83 | $disqusLanguage, 84 | $scriptAttributes 85 | ); 86 | } 87 | 88 | /** 89 | * @param string $disqusIdentifier 90 | * 91 | * @return int 92 | */ 93 | public function disqusCount( 94 | string $disqusIdentifier = "", 95 | ): int { 96 | return Disqus::$plugin->disqusService->getCommentsCount( 97 | $disqusIdentifier 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Disqus.php: -------------------------------------------------------------------------------- 1 | DisqusService::class, 70 | ]; 71 | 72 | parent::__construct($id, $parent, $config); 73 | } 74 | 75 | // Public Methods 76 | // ========================================================================= 77 | 78 | /** 79 | * @inheritdoc 80 | */ 81 | public function init(): void 82 | { 83 | parent::init(); 84 | self::$plugin = $this; 85 | Event::on( 86 | CraftVariable::class, 87 | CraftVariable::EVENT_INIT, 88 | static function(Event $event) { 89 | /** @var CraftVariable $variable */ 90 | $variable = $event->sender; 91 | $variable->set('disqus', DisqusVariable::class); 92 | } 93 | ); 94 | Craft::$app->view->registerTwigExtension(new DisqusTwigExtension()); 95 | Craft::info( 96 | Craft::t( 97 | 'disqus', 98 | '{name} plugin loaded', 99 | ['name' => $this->name] 100 | ), 101 | __METHOD__ 102 | ); 103 | } 104 | 105 | // Protected Methods 106 | // ========================================================================= 107 | 108 | /** 109 | * @inheritdoc 110 | */ 111 | protected function createSettingsModel(): Settings 112 | { 113 | return new Settings(); 114 | } 115 | 116 | /** 117 | * @inheritdoc 118 | */ 119 | protected function settingsHtml(): ?string 120 | { 121 | // Render our settings template 122 | return Craft::$app->view->renderTemplate( 123 | 'disqus/settings', 124 | [ 125 | 'settings' => $this->getSettings(), 126 | ] 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/templates/disqusEmbedTagLazy.twig: -------------------------------------------------------------------------------- 1 | 2 | 110 | 112 | -------------------------------------------------------------------------------- /src/templates/settings.twig: -------------------------------------------------------------------------------- 1 | {% import "_includes/forms" as forms %} 2 | 3 | {% macro configWarning(setting, file='disqus') -%} 4 | {%- set configArray = craft.app.config.getConfigFromFile(file) -%} 5 | {%- if configArray[setting] is defined -%} 6 | {{- "This is being overridden by the `#{setting}` setting in the `config/#{file}.php` file." |raw }} 7 | {%- else -%} 8 | {{ false }} 9 | {%- endif -%} 10 | {%- endmacro %} 11 | {% from _self import configWarning %} 12 | 13 | {{ forms.lightswitchField({ 14 | 'label': 'Lazy Load Disqus', 15 | 'instructions': 'Should the Disqus JavaScript be lazily loaded? This helps with performance, not loading the Disqus JavaScript until the user scrolls down to the comments.', 16 | 'id': 'lazyLoadDisqus', 17 | 'name': 'lazyLoadDisqus', 18 | 'on': settings['lazyLoadDisqus'], 19 | 'warning': configWarning('lazyLoadDisqus') 20 | }) }} 21 | 22 | {{ forms.textField({ 23 | 'label': 'Disqus Site Short Name', 24 | 'instructions': 'Enter your Disqus Site Short Name here.', 25 | 'id': 'disqusShortname', 26 | 'name': 'disqusShortname', 27 | 'value': settings['disqusShortname'], 28 | 'warning': configWarning('disqusShortname') 29 | }) }} 30 | 31 |Please see Integrating Single Sign-On for details on how to set up your Disqus account to use 35 | SSO
36 | 37 | {{ forms.lightswitchField({ 38 | 'label': 'Use Single Sign On', 39 | 'instructions': 'Do you want to use SSO with Disqus?', 40 | 'id': 'useSSO', 41 | 'name': 'useSSO', 42 | 'on': settings['useSSO'], 43 | 'warning': configWarning('useSSO') 44 | }) }} 45 | 46 | {{ forms.autosuggestField({ 47 | 'label': 'Disqus Public Key (API Key)', 48 | 'instructions': 'Enter your Disqus API Key.', 49 | suggestEnvVars: true, 50 | 'id': 'disqusPublicKey', 51 | 'name': 'disqusPublicKey', 52 | 'value': settings['disqusPublicKey'], 53 | 'warning': configWarning('disqusPublicKey') 54 | }) }} 55 | {{ forms.autosuggestField({ 56 | 'label': 'Disqus Secret Key (API Secret)', 57 | 'instructions': 'Enter your Disqus Secret Key (API Secret).', 58 | suggestEnvVars: true, 59 | 'id': 'disqusSecretKey', 60 | 'name': 'disqusSecretKey', 61 | 'value': settings['disqusSecretKey'], 62 | 'warning': configWarning('disqusSecretKey') 63 | }) }} 64 | 65 |Please see Adding your own SSO login and logout links for details.
69 | 70 | {{ forms.lightswitchField({ 71 | 'label': 'Use Custom Login/Logout URLs', 72 | 'instructions': 'Do you want to use custom login/logout URLs?', 73 | 'id': 'customLogin', 74 | 'name': 'customLogin', 75 | 'on': settings['customLogin'], 76 | 'warning': configWarning('customLogin') 77 | }) }} 78 | 79 | {{ forms.autosuggestField({ 80 | 'label': 'name', 81 | 'instructions': 'Your site name. We will display it in the Post As window.', 82 | 'id': 'loginName', 83 | 'name': 'loginName', 84 | 'value': settings['loginName'], 85 | 'warning': configWarning('loginName'), 86 | suggestEnvVars: true, 87 | }) }} 88 | {{ forms.autosuggestField({ 89 | 'label': 'button', 90 | 'instructions': 'Address of the image that acts as a button. Disqus 2012 users, see style guide below.', 91 | 'id': 'loginButton', 92 | 'name': 'loginButton', 93 | 'value': settings['loginButton'], 94 | 'warning': configWarning('loginButton'), 95 | suggestEnvVars: true, 96 | suggestAliases: true 97 | }) }} 98 | {{ forms.autosuggestField({ 99 | 'label': 'icon', 100 | 'instructions': 'Address of the image that appears on the login modal SSO tab. Favicons work well here. (Not required in Disqus 2012.)', 101 | 'id': 'loginIcon', 102 | 'name': 'loginIcon', 103 | 'value': settings['loginIcon'], 104 | 'warning': configWarning('loginIcon'), 105 | suggestEnvVars: true, 106 | suggestAliases: true 107 | }) }} 108 | {{ forms.autosuggestField({ 109 | 'label': 'url', 110 | 'instructions': 'Address of your login page. The page will be opened in a new window and it must close itself after authentication is done. That is how we know when it is done and reload the page.', 111 | 'id': 'loginUrl', 112 | 'name': 'loginUrl', 113 | 'value': settings['loginUrl'], 114 | 'warning': configWarning('loginUrl'), 115 | suggestEnvVars: true, 116 | suggestAliases: true 117 | }) }} 118 | {{ forms.autosuggestField({ 119 | 'label': 'logout', 120 | 'instructions': 'Address of your logout page. This page must redirect user back to the original page after logout.', 121 | 'id': 'loginLogoutUrl', 122 | 'name': 'loginLogoutUrl', 123 | 'value': settings['loginLogoutUrl'], 124 | 'warning': configWarning('loginLogoutUrl'), 125 | suggestEnvVars: true, 126 | suggestAliases: true 127 | }) }} 128 | 129 | {{ forms.textField({ 130 | 'label': 'width', 131 | 'instructions': 'Width of the login popup window. Default is 800.', 132 | 'id': 'loginWidth', 133 | 'name': 'loginWidth', 134 | 'value': settings['loginWidth'], 135 | 'warning': configWarning('loginWidth') 136 | }) }} 137 | 138 | {{ forms.textField({ 139 | 'label': 'height', 140 | 'instructions': 'Height of the login popup window. Default is 400.', 141 | 'id': 'loginHeight', 142 | 'name': 'loginHeight', 143 | 'value': settings['loginHeight'], 144 | 'warning': configWarning('loginHeight') 145 | }) }} 146 | -------------------------------------------------------------------------------- /src/models/Settings.php: -------------------------------------------------------------------------------- 1 | disqusSecretKey); 103 | } 104 | 105 | /** 106 | * @return string the parsed public key (e.g. 'XXXXXXXXXXX') 107 | */ 108 | public function getDisqusPublicKey(): string 109 | { 110 | return App::parseEnv($this->disqusPublicKey); 111 | } 112 | 113 | /** 114 | * @return string 115 | */ 116 | public function getDisqusShortname(): string 117 | { 118 | return $this->disqusShortname; 119 | } 120 | 121 | /** 122 | * @return bool 123 | */ 124 | public function getUseSSO(): bool 125 | { 126 | return $this->useSSO; 127 | } 128 | 129 | /** 130 | * @return bool 131 | */ 132 | public function getCustomLogin(): bool 133 | { 134 | return $this->customLogin; 135 | } 136 | 137 | /** 138 | * @return string 139 | */ 140 | public function getLoginName(): string 141 | { 142 | return App::parseEnv($this->loginName); 143 | } 144 | 145 | /** 146 | * @return string 147 | */ 148 | public function getLoginButton(): string 149 | { 150 | return App::parseEnv($this->loginButton); 151 | } 152 | 153 | /** 154 | * @return string 155 | */ 156 | public function getLoginIcon(): string 157 | { 158 | return App::parseEnv($this->loginIcon); 159 | } 160 | 161 | /** 162 | * @return string 163 | */ 164 | public function getLoginUrl(): string 165 | { 166 | return App::parseEnv($this->loginUrl); 167 | } 168 | 169 | /** 170 | * @return string 171 | */ 172 | public function getLoginLogoutUrl(): string 173 | { 174 | return App::parseEnv($this->loginLogoutUrl); 175 | } 176 | 177 | /** 178 | * @return int 179 | */ 180 | public function getLoginWidth(): int 181 | { 182 | return $this->loginWidth; 183 | } 184 | 185 | /** 186 | * @return int 187 | */ 188 | public function getLoginHeight(): int 189 | { 190 | return $this->loginHeight; 191 | } 192 | 193 | /** 194 | * @inheritdoc 195 | */ 196 | public function rules(): array 197 | { 198 | return [ 199 | ['lazyLoadDisqus', 'boolean'], 200 | ['lazyLoadDisqus', 'default', 'value' => false], 201 | ['disqusShortname', 'string'], 202 | ['disqusShortname', 'default', 'value' => ''], 203 | ['useSSO', 'boolean'], 204 | ['useSSO', 'default', 'value' => false], 205 | ['disqusPublicKey', 'string'], 206 | ['disqusPublicKey', 'default', 'value' => ''], 207 | ['disqusSecretKey', 'string'], 208 | ['disqusSecretKey', 'default', 'value' => ''], 209 | ['customLogin', 'boolean'], 210 | ['customLogin', 'default', 'value' => false], 211 | ['loginName', 'string'], 212 | ['loginName', 'default', 'value' => ''], 213 | ['loginButton', 'string'], 214 | ['loginButton', 'default', 'value' => ''], 215 | ['loginIcon', 'string'], 216 | ['loginIcon', 'default', 'value' => ''], 217 | ['loginUrl', 'string'], 218 | ['loginUrl', 'default', 'value' => ''], 219 | ['loginLogoutUrl', 'string'], 220 | ['loginLogoutUrl', 'default', 'value' => ''], 221 | ['loginWidth', 'integer', 'min' => 400, 'max' => 2000], 222 | ['loginWidth', 'default', 'value' => 800], 223 | ['loginHeight', 'integer', 'min' => 200, 'max' => 1000], 224 | ['loginHeight', 'default', 'value' => 400], 225 | ]; 226 | } 227 | 228 | /** 229 | * @return array 230 | */ 231 | public function behaviors(): array 232 | { 233 | return [ 234 | 'parser' => [ 235 | 'class' => EnvAttributeParserBehavior::class, 236 | 'attributes' => [ 237 | 'loginName', 238 | 'loginButton', 239 | 'loginIcon', 240 | 'loginUrl', 241 | 'loginLogoutUrl', 242 | 'disqusPublicKey', 243 | 'disqusSecretKey', 244 | ], 245 | ], 246 | 'typecast' => [ 247 | 'class' => AttributeTypecastBehavior::class, 248 | // 'attributeTypes' will be composed automatically according to `rules()` 249 | ], 250 | ]; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/services/DisqusService.php: -------------------------------------------------------------------------------- 1 | getSettings(); 58 | $disqusShortname = $settings->getDisqusShortName(); 59 | 60 | $vars = [ 61 | 'disqusShortname' => $disqusShortname, 62 | 'disqusIdentifier' => $disqusIdentifier, 63 | 'disqusTitle' => $disqusTitle, 64 | 'disqusUrl' => $disqusUrl, 65 | 'disqusCategoryId' => $disqusCategoryId, 66 | 'disqusLanguage' => $disqusLanguage, 67 | 'scriptAttributes' => Html::renderTagAttributes($scriptAttributes), 68 | ]; 69 | $vars = array_merge($vars, $this->getSSOVars()); 70 | $templateName = 'disqusEmbedTag'; 71 | if ($settings->lazyLoadDisqus) { 72 | $templateName = 'disqusEmbedTagLazy'; 73 | } 74 | 75 | return $this->renderPluginTemplate($templateName, $vars); 76 | } 77 | 78 | /** 79 | * Return the number of comments for a particular thread 80 | * 81 | * @param string $disqusIdentifier 82 | * 83 | * @return int 84 | * @noinspection PhpComposerExtensionStubsInspection 85 | */ 86 | public function getCommentsCount( 87 | string $disqusIdentifier = "", 88 | ): int { 89 | /** @var Settings $settings */ 90 | $settings = Disqus::$plugin->getSettings(); 91 | if (!empty($settings->getDisqusPublicKey())) { 92 | $disqusShortname = $settings->getDisqusShortname(); 93 | $apiKey = $settings->getDisqusPublicKey(); 94 | 95 | $url = "https://disqus.com/api/3.0/threads/details.json?api_key=" 96 | . $apiKey 97 | . "&forum=" . $disqusShortname 98 | . "&thread:ident=" 99 | . $disqusIdentifier; 100 | 101 | $ch = curl_init(); 102 | curl_setopt($ch, CURLOPT_URL, $url); 103 | curl_setopt($ch, CURLOPT_HEADER, 0); 104 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 105 | $return = curl_exec($ch); 106 | curl_close($ch); 107 | 108 | $json = json_decode($return, true); 109 | if ($json !== null && !empty($json["code"]) && $json["code"] == 0) { 110 | return $json["response"]["posts"]; 111 | } else { 112 | Craft::error(Craft::t('disqus', print_r($json, true)), __METHOD__); 113 | 114 | return 0; 115 | } 116 | } else { 117 | Craft::error(Craft::t('disqus', "Public API Key missing"), __METHOD__); 118 | 119 | return 0; 120 | } 121 | } 122 | 123 | // Protected Methods 124 | // ========================================================================= 125 | 126 | /** 127 | * Return the SSO vars 128 | * 129 | * @return array 130 | */ 131 | protected function getSSOVars(): array 132 | { 133 | /** @var Settings $settings */ 134 | $settings = Disqus::$plugin->getSettings(); 135 | $vars = [ 136 | 'useSSO' => false, 137 | 'useCustomLogin' => false, 138 | ]; 139 | if ($settings->getUseSSO()) { 140 | $data = []; 141 | 142 | // Set the data array 143 | /** @var User $user */ 144 | $user = Craft::$app->getUser(); 145 | $currentUser = $user->getIdentity(); 146 | if ($currentUser) { 147 | $data['id'] = $currentUser->id; 148 | if (Craft::$app->getConfig()->getGeneral()->useEmailAsUsername) { 149 | $data['username'] = $currentUser->getFullName(); 150 | } else { 151 | $data['username'] = $currentUser->username; 152 | } 153 | $data['email'] = $currentUser->email; 154 | try { 155 | $data['avatar'] = $currentUser->getPhoto()->getUrl(); 156 | } catch (InvalidConfigException $e) { 157 | } 158 | } 159 | 160 | // Encode the data array and generate the hMac 161 | $message = base64_encode(json_encode($data)); 162 | $timestamp = time(); 163 | $hMac = $this->disqusHmacSha1( 164 | $message 165 | . ' ' 166 | . $timestamp, 167 | $settings->getDisqusSecretKey() 168 | ); 169 | 170 | // Set the vars for the template 171 | $vars = array_merge($vars, [ 172 | 'useSSO' => true, 173 | 'message' => $message, 174 | 'hmac' => $hMac, 175 | 'timestamp' => $timestamp, 176 | 'disqusPublicKey' => $settings->getDisqusPublicKey(), 177 | ]); 178 | 179 | // Set the vars for the custom login 180 | if ($settings->getCustomLogin()) { 181 | $vars = array_merge($vars, [ 182 | 'useCustomLogin' => true, 183 | 'loginName' => $settings->getLoginName(), 184 | 'loginButton' => $settings->getLoginButton(), 185 | 'loginIcon' => $settings->getLoginIcon(), 186 | 'loginUrl' => $settings->getLoginUrl(), 187 | 'loginLogoutUrl' => $settings->getLoginLogoutUrl(), 188 | 'loginWidth' => $settings->getLoginWidth(), 189 | 'loginHeight' => $settings->getLoginHeight(), 190 | ]); 191 | } 192 | } 193 | 194 | return $vars; 195 | } 196 | 197 | /** 198 | * Render a plugin template 199 | * 200 | * @param $templatePath 201 | * @param $vars 202 | * 203 | * @return Markup 204 | */ 205 | protected function renderPluginTemplate($templatePath, $vars): Markup 206 | { 207 | // Stash the old template mode, and set it Control Panel template mode 208 | $oldMode = Craft::$app->view->getTemplateMode(); 209 | try { 210 | Craft::$app->view->setTemplateMode(View::TEMPLATE_MODE_CP); 211 | } catch (Exception $e) { 212 | Craft::error($e->getMessage(), __METHOD__); 213 | } 214 | 215 | // Render the template with our vars passed in 216 | try { 217 | $htmlText = Craft::$app->view->renderTemplate('disqus/' . $templatePath, $vars); 218 | } catch (\Exception $e) { 219 | $htmlText = 'Error rendering template ' . $templatePath . ' -> ' . $e->getMessage(); 220 | Craft::error(Craft::t('disqus', $htmlText), __METHOD__); 221 | } 222 | 223 | // Restore the old template mode 224 | try { 225 | Craft::$app->view->setTemplateMode($oldMode); 226 | } catch (Exception $e) { 227 | Craft::error($e->getMessage(), __METHOD__); 228 | } 229 | 230 | return Template::raw($htmlText); 231 | } 232 | 233 | /** 234 | * HMAC->SHA1 235 | * From: 236 | * https://github.com/disqus/DISQUS-API-Recipes/blob/master/sso/php/sso.php 237 | * 238 | * @param $data 239 | * @param $key 240 | * 241 | * @return string 242 | */ 243 | protected function disqusHmacSha1($data, $key): string 244 | { 245 | $blockSize = 64; 246 | $hashFunc = 'sha1'; 247 | if (strlen($key) > $blockSize) { 248 | $key = pack('H*', $hashFunc($key)); 249 | } 250 | $key = str_pad($key, $blockSize, chr(0x00)); 251 | $iPad = str_repeat(chr(0x36), $blockSize); 252 | $oPad = str_repeat(chr(0x5c), $blockSize); 253 | $hMac = pack( 254 | 'H*', 255 | $hashFunc( 256 | ($key ^ $oPad) . pack( 257 | 'H*', 258 | $hashFunc( 259 | ($key ^ $iPad) . $data 260 | ) 261 | ) 262 | ) 263 | ); 264 | 265 | return bin2hex($hMac); 266 | } 267 | } 268 | --------------------------------------------------------------------------------