diff --git a/.gitignore b/.gitignore index 718851a42c..0628e588ef 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ nbproject .idea/* .gitmodules .github -/composer.lock themes/* !themes/HumHub diff --git a/.htaccess.dist b/.htaccess.dist index 3ae422a889..50b29c0cf8 100644 --- a/.htaccess.dist +++ b/.htaccess.dist @@ -11,6 +11,10 @@ RewriteEngine on # prevent httpd from serving dotfiles (.htaccess, .svn, .git, etc.) - except let's encrypt challenge RedirectMatch 403 ^/?\.(?!/well-known/acme-challenge/[\w-]{43}$) +# ensure permalink when url rewriting was enabled (index.php?r=content/perma&id=6 => /content/perma/?id=6 +RewriteCond %{QUERY_STRING} ^r=content(/|%2)perma&id=([0-9]*)$ +RewriteRule ^index\.php$ %{REQUEST_URI}/content/perma/?id=%2 [R=302,L] + RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ RewriteRule ^(.*) - [E=BASE:%1] diff --git a/composer.json b/composer.json index d8cc4f1639..46dc197ff4 100644 --- a/composer.json +++ b/composer.json @@ -2,14 +2,13 @@ "name": "humhub/humhub", "description": "HumHub - The flexible Open Source Social Network Kit for Collaboration", "keywords": ["humhub", "yii2", "framework"], - "homepage": "http://www.humhub.com/", + "homepage": "https://www.humhub.org/", "type": "project", "license": "AGPL-3.0", "support": { "issues": "https://github.com/humhub/humhub/issues?state=open", - "forum": "http://community.humhub.com", - "wiki": "http://community.humhub.com", - "irc": "irc://irc.freenode.net/humhub", + "forum": "https://community.humhub.com", + "wiki": "https://community.humhub.com", "source": "https://github.com/humhub/humhub" }, "minimum-stability": "stable", @@ -24,35 +23,39 @@ "raoul2000/yii2-jcrop-widget": "*", "kartik-v/yii2-widgets": "*", "phpoffice/phpexcel": "*", - "cebe/markdown": "1.0.2", + "cebe/markdown": "~1.0.2", "yiisoft/yii2-jui": "~2.0.0", "zendframework/zend-http": "*", "jbroadway/urlify": "^1.0", "nqxcode/zendsearch": "^2.0", "xj/yii2-jplayer-widget": "*", "zendframework/zend-ldap": "^2.5", - "zhuravljov/yii2-queue": "^0.11", - "bower-asset/jquery-timeago": "1.4.*", + "bower-asset/jquery-timeago": "1.5.*", "bower-asset/jquery-nicescroll": "3.6.*", "bower-asset/jquery-knob": "1.2.*", "bower-asset/jquery-placeholder": "^2.3.0", - "bower-asset/blueimp-file-upload": "9.11.*", + "bower-asset/blueimp-file-upload": "9.18.*", "bower-asset/fontawesome": "^4.7.0", "bower-asset/bootstrap-markdown": "2.10.*", - "bower-asset/select2": "^4.0.2", + "bower-asset/select2": "^4.0.4", "bower-asset/bluebird": "^3.3.5", "bower-asset/select2-bootstrap-theme": "0.1.0-beta.4", "bower-asset/jquery.cookie": "^1.4.1", "bower-asset/jquery-color": "^2.1.2", "bower-asset/autosize": "1.*", "bower-asset/nprogress": "*", - "bower-asset/At.js": "^1.5.1", "bower-asset/animate.css": "*", "bower-asset/html5shiv": "^3.7", "bower-asset/clipboard.js": "*", - "bower-asset/jPlayer": "2.9.2", + "bower-asset/jPlayer": "2.9.*", "bower-asset/imagesloaded": "*", - "bower-asset/jquery-timeentry": "^2.0" + "bower-asset/jquery-timeentry": "^2.0", + "bower-asset/caret.js": "0.2.2", + "npm-asset/at.js": "^1.5.1", + "yiisoft/yii2-queue": "^2.0", + "yiisoft/yii2-redis": "^2.0", + "firebase/php-jwt": "^5.0", + "npm-asset/socket.io-client": "^2.0" }, "require-dev": { "codeception/codeception": "*", @@ -61,20 +64,18 @@ "yiisoft/yii2-faker": "~2.0.0", "yiisoft/yii2-apidoc": "~2.0.0" }, - "config": { - "process-timeout": 1800, - "vendor-dir": "protected/vendor", - "fxp-asset":{ - "installer-paths": { - "npm-asset-library": "protected/vendor/npm", - "bower-asset-library": "protected/vendor/bower" - }, - "vcs-driver-options": { - "github-no-api": true - }, - "git-skip-update": "2 days", - "pattern-skip-version": "(-build|-patch)" + "repositories": [ + { + "type": "composer", + "url": "https://asset-packagist.org" } + ], + "config": { + "platform": { + "php": "5.6" + }, + "process-timeout": 1800, + "vendor-dir": "protected/vendor" }, "scripts": { "post-create-project-cmd": [ @@ -94,10 +95,6 @@ "generateCookieValidationKey": [ "protected/config/web.php" ] - }, - "asset-installer-paths": { - "npm-asset-library": "protected/vendor/npm", - "bower-asset-library": "protected/vendor/bower" } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000000..2b92b48f18 --- /dev/null +++ b/composer.lock @@ -0,0 +1,6605 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "b68f91921c656fad8b732bfc1fa02f6a", + "content-hash": "1951bbba7564b5fb1fbf8c4fc1e0d1b8", + "packages": [ + { + "name": "bower-asset/animate.css", + "version": "3.5.2", + "source": { + "type": "git", + "url": "https://github.com/daneden/animate.css.git", + "reference": "dac3dab7b59cb6072b5d0fe23eae3805e370a58c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/daneden/animate.css/zipball/dac3dab7b59cb6072b5d0fe23eae3805e370a58c", + "reference": "dac3dab7b59cb6072b5d0fe23eae3805e370a58c", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/autosize", + "version": "1.18.18", + "source": { + "type": "git", + "url": "https://github.com/jackmoore/autosize.git", + "reference": "64e9f33aba55f6aebcf34e91bc811296089e6ec8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jackmoore/autosize/zipball/64e9f33aba55f6aebcf34e91bc811296089e6ec8", + "reference": "64e9f33aba55f6aebcf34e91bc811296089e6ec8", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/bluebird", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/petkaantonov/bluebird.git", + "reference": "6320ed7613aa27d3ec375d7bf166473c5c356aef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/petkaantonov/bluebird/zipball/6320ed7613aa27d3ec375d7bf166473c5c356aef", + "reference": "6320ed7613aa27d3ec375d7bf166473c5c356aef", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/blueimp-canvas-to-blob", + "version": "v3.14.0", + "source": { + "type": "git", + "url": "https://github.com/blueimp/JavaScript-Canvas-to-Blob.git", + "reference": "9f3deb2e710d39c72a988559609d9dc9a319de0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blueimp/JavaScript-Canvas-to-Blob/zipball/9f3deb2e710d39c72a988559609d9dc9a319de0b", + "reference": "9f3deb2e710d39c72a988559609d9dc9a319de0b", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/blueimp-file-upload", + "version": "v9.18.0", + "source": { + "type": "git", + "url": "https://github.com/blueimp/jQuery-File-Upload.git", + "reference": "0b4af3c57b86b3c7147c4d7c75deb71a0133f0e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blueimp/jQuery-File-Upload/zipball/0b4af3c57b86b3c7147c4d7c75deb71a0133f0e3", + "reference": "0b4af3c57b86b3c7147c4d7c75deb71a0133f0e3", + "shasum": null + }, + "require": { + "bower-asset/blueimp-canvas-to-blob": ">=2.1.1", + "bower-asset/blueimp-load-image": ">=1.13.0", + "bower-asset/blueimp-tmpl": ">=2.5.4", + "bower-asset/jquery": ">=1.6" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/blueimp-load-image", + "version": "v2.17.0", + "source": { + "type": "git", + "url": "https://github.com/blueimp/JavaScript-Load-Image.git", + "reference": "0da08e062d47ba61f68e3971980e9fd8c05218aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blueimp/JavaScript-Load-Image/zipball/0da08e062d47ba61f68e3971980e9fd8c05218aa", + "reference": "0da08e062d47ba61f68e3971980e9fd8c05218aa", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/blueimp-tmpl", + "version": "v3.11.0", + "source": { + "type": "git", + "url": "https://github.com/blueimp/JavaScript-Templates.git", + "reference": "da7647cb93fff2030e73a701db22c66de76a9919" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/blueimp/JavaScript-Templates/zipball/da7647cb93fff2030e73a701db22c66de76a9919", + "reference": "da7647cb93fff2030e73a701db22c66de76a9919", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/bootstrap", + "version": "v3.3.7", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.9.1,<4.0" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/bootstrap-markdown", + "version": "v2.10.0", + "source": { + "type": "git", + "url": "https://github.com/toopay/bootstrap-markdown.git", + "reference": "3f69bb3db5020d29182c2092040f0ad8d390bda7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/toopay/bootstrap-markdown/zipball/3f69bb3db5020d29182c2092040f0ad8d390bda7", + "reference": "3f69bb3db5020d29182c2092040f0ad8d390bda7", + "shasum": null + }, + "require": { + "bower-asset/bootstrap": "~3" + }, + "type": "bower-asset", + "license": [ + "Apache-2.0" + ] + }, + { + "name": "bower-asset/caret.js", + "version": "v0.2.2", + "source": { + "type": "git", + "url": "https://github.com/ichord/Caret.js.git", + "reference": "2af4966a6bfb61d23eb6da6031add29bb3b14bcb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ichord/Caret.js/zipball/2af4966a6bfb61d23eb6da6031add29bb3b14bcb", + "reference": "2af4966a6bfb61d23eb6da6031add29bb3b14bcb", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7.0" + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/clipboard.js", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/lgarron/clipboard-polyfill.git", + "reference": "d0f973f6e2a2aebd7bee126af28e3ad490e63031" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lgarron/clipboard-polyfill/zipball/d0f973f6e2a2aebd7bee126af28e3ad490e63031", + "reference": "d0f973f6e2a2aebd7bee126af28e3ad490e63031", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/ev-emitter", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/metafizzy/ev-emitter.git", + "reference": "1baa3a03d8e07f665b0eb797661fcc2b0d2a5736" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/metafizzy/ev-emitter/zipball/1baa3a03d8e07f665b0eb797661fcc2b0d2a5736", + "reference": "1baa3a03d8e07f665b0eb797661fcc2b0d2a5736", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/fontawesome", + "version": "v4.7.0", + "source": { + "type": "git", + "url": "https://github.com/FortAwesome/Font-Awesome.git", + "reference": "a8386aae19e200ddb0f6845b5feeee5eb7013687" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FortAwesome/Font-Awesome/zipball/a8386aae19e200ddb0f6845b5feeee5eb7013687", + "reference": "a8386aae19e200ddb0f6845b5feeee5eb7013687", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "OFL-1.1", + "MIT", + "CC-BY-3.0" + ] + }, + { + "name": "bower-asset/html5shiv", + "version": "3.7.3", + "source": { + "type": "git", + "url": "https://github.com/aFarkas/html5shiv.git", + "reference": "ed28c56c071bddfe7d635ad88995674957a016be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aFarkas/html5shiv/zipball/ed28c56c071bddfe7d635ad88995674957a016be", + "reference": "ed28c56c071bddfe7d635ad88995674957a016be", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/imagesloaded", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/desandro/imagesloaded.git", + "reference": "a8e0dd3a463903014bf8071efece15dcd570b1b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/desandro/imagesloaded/zipball/a8e0dd3a463903014bf8071efece15dcd570b1b9", + "reference": "a8e0dd3a463903014bf8071efece15dcd570b1b9", + "shasum": null + }, + "require": { + "bower-asset/ev-emitter": ">=1.0.0,<2.0.0" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/jplayer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/jplayer/jPlayer.git", + "reference": "34e55cd7694552447bd0dbc38d886fdc96df0d06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jplayer/jPlayer/zipball/34e55cd7694552447bd0dbc38d886fdc96df0d06", + "reference": "34e55cd7694552447bd0dbc38d886fdc96df0d06", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7.2" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/jquery", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/jquery-color", + "version": "2.1.2", + "source": { + "type": "git", + "url": "git@github.com:jquery/jquery-color.git", + "reference": "9e5e04c1c4ee66427fbe04cfce8155b76748ca64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery-color/zipball/9e5e04c1c4ee66427fbe04cfce8155b76748ca64", + "reference": "9e5e04c1c4ee66427fbe04cfce8155b76748ca64", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/jquery-knob", + "version": "1.2.13", + "source": { + "type": "git", + "url": "https://github.com/aterrien/jQuery-Knob.git", + "reference": "755309e933d326ffaa5a2d758dc377147b766515" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aterrien/jQuery-Knob/zipball/755309e933d326ffaa5a2d758dc377147b766515", + "reference": "755309e933d326ffaa5a2d758dc377147b766515", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7.0" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/jquery-nicescroll", + "version": "v3.6.8-patch1", + "source": { + "type": "git", + "url": "https://github.com/inuyaksa/jquery.nicescroll.git", + "reference": "25737c1a8ecfaccf219a13142d300b64e52e6b58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inuyaksa/jquery.nicescroll/zipball/25737c1a8ecfaccf219a13142d300b64e52e6b58", + "reference": "25737c1a8ecfaccf219a13142d300b64e52e6b58", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.8.3" + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/jquery-placeholder", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "git@github.com:mathiasbynens/jquery-placeholder.git", + "reference": "bd18712c2f6cc6be2dfd7310a89044b09610057e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mathiasbynens/jquery-placeholder/zipball/bd18712c2f6cc6be2dfd7310a89044b09610057e", + "reference": "bd18712c2f6cc6be2dfd7310a89044b09610057e", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.6" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/jquery-timeago", + "version": "v1.5.4", + "source": { + "type": "git", + "url": "https://github.com/rmm5t/jquery-timeago.git", + "reference": "180864a9c544a49e43719b457250af216d5e4c3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rmm5t/jquery-timeago/zipball/180864a9c544a49e43719b457250af216d5e4c3a", + "reference": "180864a9c544a49e43719b457250af216d5e4c3a", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.4" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/jquery-timeentry", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/rick20/timeentry.git", + "reference": "1d5e51ab2bbe877567ce66511a56d65e1fe82a92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rick20/timeentry/zipball/1d5e51ab2bbe877567ce66511a56d65e1fe82a92", + "reference": "1d5e51ab2bbe877567ce66511a56d65e1fe82a92", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/jquery-ui", + "version": "1.11.4", + "source": { + "type": "git", + "url": "https://github.com/components/jqueryui.git", + "reference": "c34f8dbf3ba57b3784b93f26119f436c0e8288e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/components/jqueryui/zipball/c34f8dbf3ba57b3784b93f26119f436c0e8288e1", + "reference": "c34f8dbf3ba57b3784b93f26119f436c0e8288e1", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.6" + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/jquery.cookie", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "git@github.com:carhartl/jquery-cookie.git", + "reference": "7f88a4e631aba8a8c688fd8999ce6b9bcfd50718" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/carhartl/jquery-cookie/zipball/7f88a4e631aba8a8c688fd8999ce6b9bcfd50718", + "reference": "7f88a4e631aba8a8c688fd8999ce6b9bcfd50718", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.2" + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/jquery.inputmask", + "version": "3.3.8", + "source": { + "type": "git", + "url": "https://github.com/RobinHerbots/Inputmask.git", + "reference": "791d84990c4a98df1597e9d155be53a3725805dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/791d84990c4a98df1597e9d155be53a3725805dd", + "reference": "791d84990c4a98df1597e9d155be53a3725805dd", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset", + "license": [ + "http://opensource.org/licenses/mit-license.php" + ] + }, + { + "name": "bower-asset/nprogress", + "version": "v0.2.0", + "source": { + "type": "git", + "url": "https://github.com/rstacruz/nprogress.git", + "reference": "254266ea50c220f6507fa3f2978fc530147c7f06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rstacruz/nprogress/zipball/254266ea50c220f6507fa3f2978fc530147c7f06", + "reference": "254266ea50c220f6507fa3f2978fc530147c7f06", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/punycode", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/bestiejs/punycode.js.git", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/select2", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/select2/select2.git", + "reference": "16f6b10628f9000918708cef9b3da615119cf6d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/select2/select2/zipball/16f6b10628f9000918708cef9b3da615119cf6d6", + "reference": "16f6b10628f9000918708cef9b3da615119cf6d6", + "shasum": null + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "bower-asset/select2-bootstrap-theme", + "version": "0.1.0-beta.4", + "source": { + "type": "git", + "url": "git@github.com:fk/select2-bootstrap-theme.git", + "reference": "1a81f99b47a16d04554c4ec57d5416db4ce2256c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fk/select2-bootstrap-theme/zipball/1a81f99b47a16d04554c4ec57d5416db4ce2256c", + "reference": "1a81f99b47a16d04554c4ec57d5416db4ce2256c", + "shasum": null + }, + "type": "bower-asset" + }, + { + "name": "bower-asset/yii2-pjax", + "version": "v2.0.7", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/jquery-pjax.git", + "reference": "885fc8c2d36c93a801b6af0ee8ad55d79df97cb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/885fc8c2d36c93a801b6af0ee8ad55d79df97cb1", + "reference": "885fc8c2d36c93a801b6af0ee8ad55d79df97cb1", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.8" + }, + "type": "bower-asset", + "license": [ + "MIT" + ] + }, + { + "name": "cebe/markdown", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown.git", + "reference": "f681fee8303310415b746f3758eeda0a7ad08bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/f681fee8303310415b746f3758eeda0a7ad08bda", + "reference": "f681fee8303310415b746f3758eeda0a7ad08bda", + "shasum": "" + }, + "require": { + "lib-pcre": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "cebe/indent": "*", + "facebook/xhprof": "*@dev", + "phpunit/phpunit": "3.7.*" + }, + "bin": [ + "bin/markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP", + "homepage": "https://github.com/cebe/markdown#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "markdown", + "markdown-extra" + ], + "time": "2015-03-06 05:21:16" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14 19:40:03" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/95e1bae3182efc0f3422896a3236e991049dac69", + "reference": "95e1bae3182efc0f3422896a3236e991049dac69", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "^1.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "time": "2017-06-03 02:28:16" + }, + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "time": "2017-06-27 22:17:23" + }, + { + "name": "imagine/imagine", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/avalanche123/Imagine.git", + "reference": "f64ec666baaa800edcbf237db41121a569230709" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/avalanche123/Imagine/zipball/f64ec666baaa800edcbf237db41121a569230709", + "reference": "f64ec666baaa800edcbf237db41121a569230709", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "sami/sami": "dev-master" + }, + "suggest": { + "ext-gd": "to use the GD implementation", + "ext-gmagick": "to use the Gmagick implementation", + "ext-imagick": "to use the Imagick implementation" + }, + "type": "library", + "autoload": { + "psr-0": { + "Imagine": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bulat Shakirzyanov", + "email": "mallluhuct@gmail.com", + "homepage": "http://avalanche123.com" + } + ], + "description": "Image processing for PHP 5.3", + "homepage": "http://imagine.readthedocs.org/", + "keywords": [ + "drawing", + "graphics", + "image manipulation", + "image processing" + ], + "time": "2013-07-10 17:25:36" + }, + { + "name": "jbroadway/urlify", + "version": "1.1.0-stable", + "source": { + "type": "git", + "url": "https://github.com/jbroadway/urlify.git", + "reference": "99bb78cd9002d0e9ce479bb81635eb665e37e981" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jbroadway/urlify/zipball/99bb78cd9002d0e9ce479bb81635eb665e37e981", + "reference": "99bb78cd9002d0e9ce479bb81635eb665e37e981", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "URLify": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Johnny Broadway", + "email": "johnny@johnnybroadway.com", + "homepage": "http://www.johnnybroadway.com/" + } + ], + "description": "PHP port of URLify.js from the Django project. Transliterates non-ascii characters for use in URLs.", + "homepage": "https://github.com/jbroadway/urlify", + "keywords": [ + "encode", + "iconv", + "link", + "slug", + "translit", + "transliterate", + "transliteration", + "url", + "urlify" + ], + "time": "2017-01-03 20:12:54" + }, + { + "name": "kartik-v/bootstrap-fileinput", + "version": "v4.4.5", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/bootstrap-fileinput.git", + "reference": "47377ede9533f2060ed8a132a925e4efa60cbd77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/bootstrap-fileinput/zipball/47377ede9533f2060ed8a132a925e4efa60cbd77", + "reference": "47377ede9533f2060ed8a132a925e4efa60cbd77", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\plugins\\fileinput\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "An enhanced HTML 5 file input for Bootstrap 3.x with features for file preview for many file types, multiple selection, ajax uploads, and more.", + "homepage": "https://github.com/kartik-v/bootstrap-fileinput", + "keywords": [ + "ajax", + "bootstrap", + "delete", + "file", + "image", + "input", + "jquery", + "multiple", + "preview", + "progress", + "upload" + ], + "time": "2017-10-01 12:55:30" + }, + { + "name": "kartik-v/bootstrap-star-rating", + "version": "v4.0.2", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/bootstrap-star-rating.git", + "reference": "599c10e2456bc2215da7c6337d6b1c65892bff72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/bootstrap-star-rating/zipball/599c10e2456bc2215da7c6337d6b1c65892bff72", + "reference": "599c10e2456bc2215da7c6337d6b1c65892bff72", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A simple yet powerful JQuery star rating plugin for Bootstrap.", + "homepage": "https://github.com/kartik-v/bootstrap-star-rating", + "keywords": [ + "Rating", + "awesome", + "bootstrap", + "font", + "glyphicon", + "star", + "svg" + ], + "time": "2017-08-27 08:23:59" + }, + { + "name": "kartik-v/dependent-dropdown", + "version": "v1.4.8", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/dependent-dropdown.git", + "reference": "1349e7f5816a65e89bc227098386aac5b2c89363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/dependent-dropdown/zipball/1349e7f5816a65e89bc227098386aac5b2c89363", + "reference": "1349e7f5816a65e89bc227098386aac5b2c89363", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\plugins\\depdrop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A multi level dependent dropdown JQuery plugin that allows nested dependencies.", + "homepage": "https://github.com/kartik-v/dependent-dropdown", + "keywords": [ + "dependent", + "dropdown", + "jquery", + "option", + "select" + ], + "time": "2017-08-01 11:43:55" + }, + { + "name": "kartik-v/yii2-krajee-base", + "version": "v1.8.9", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-krajee-base.git", + "reference": "0cafe6376780cedcd00f52c9d82b172d5851f6b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-krajee-base/zipball/0cafe6376780cedcd00f52c9d82b172d5851f6b0", + "reference": "0cafe6376780cedcd00f52c9d82b172d5851f6b0", + "shasum": "" + }, + "require": { + "yiisoft/yii2-bootstrap": "@dev" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\base\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Base library and foundation components for all Yii2 Krajee extensions.", + "homepage": "https://github.com/kartik-v/yii2-krajee-base", + "keywords": [ + "base", + "extension", + "foundation", + "krajee", + "widget", + "yii2" + ], + "time": "2017-09-29 06:18:14" + }, + { + "name": "kartik-v/yii2-widget-activeform", + "version": "v1.4.8", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-activeform.git", + "reference": "53c2f877f12ba0b79e8346b6cae50cbba2bcfc69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-activeform/zipball/53c2f877f12ba0b79e8346b6cae50cbba2bcfc69", + "reference": "53c2f877f12ba0b79e8346b6cae50cbba2bcfc69", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\form\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 active-form and active-field with full bootstrap styling support (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-activeform", + "keywords": [ + "activefield", + "activeform", + "extension", + "field", + "form", + "widget", + "yii2" + ], + "time": "2016-04-27 18:38:05" + }, + { + "name": "kartik-v/yii2-widget-affix", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-affix.git", + "reference": "2184119bfa518c285406156f744769b13b861712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-affix/zipball/2184119bfa518c285406156f744769b13b861712", + "reference": "2184119bfa518c285406156f744769b13b861712", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\affix\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A scrollspy and affixed enhanced navigation to highlight page sections (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-affix", + "keywords": [ + "affix", + "bootstrap", + "extension", + "jquery", + "navigation", + "plugin", + "scrollspy", + "widget", + "yii2" + ], + "time": "2014-11-09 04:56:27" + }, + { + "name": "kartik-v/yii2-widget-alert", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-alert.git", + "reference": "7348b0d047695a3e552888c481ce250cbc1f9d0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-alert/zipball/7348b0d047695a3e552888c481ce250cbc1f9d0d", + "reference": "7348b0d047695a3e552888c481ce250cbc1f9d0d", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\alert\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A widget to generate alert based notifications using bootstrap-alert plugin (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-alert", + "keywords": [ + "alert", + "block", + "bootstrap", + "extension", + "flash", + "jquery", + "notification", + "plugin", + "widget", + "yii2" + ], + "time": "2017-03-10 17:08:52" + }, + { + "name": "kartik-v/yii2-widget-colorinput", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-colorinput.git", + "reference": "3f6e847ef72cf6e27e4d3b4870b00b8f80d51752" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-colorinput/zipball/3f6e847ef72cf6e27e4d3b4870b00b8f80d51752", + "reference": "3f6e847ef72cf6e27e4d3b4870b00b8f80d51752", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\color\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "An enhanced Yii 2 widget encapsulating the HTML 5 color input (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-colorinput", + "keywords": [ + "HTML5", + "color", + "extension", + "form", + "input", + "jquery", + "plugin", + "widget", + "yii2" + ], + "time": "2016-01-14 11:15:49" + }, + { + "name": "kartik-v/yii2-widget-datepicker", + "version": "v1.4.3", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-datepicker.git", + "reference": "b793655b63e77e5598ed232cf887cc640c30420f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-datepicker/zipball/b793655b63e77e5598ed232cf887cc640c30420f", + "reference": "b793655b63e77e5598ed232cf887cc640c30420f", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\date\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the bootstrap datepicker plugin (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-datepicker", + "keywords": [ + "date", + "extension", + "form", + "jquery", + "picker", + "plugin", + "select2", + "widget", + "yii2" + ], + "time": "2017-09-04 03:28:47" + }, + { + "name": "kartik-v/yii2-widget-datetimepicker", + "version": "v1.4.4", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-datetimepicker.git", + "reference": "e843520aca008dc0807aa7ea99bfab2cc03c9a3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-datetimepicker/zipball/e843520aca008dc0807aa7ea99bfab2cc03c9a3c", + "reference": "e843520aca008dc0807aa7ea99bfab2cc03c9a3c", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\datetime\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the bootstrap datetimepicker plugin (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-datetimepicker", + "keywords": [ + "datetime", + "extension", + "form", + "jquery", + "picker", + "plugin", + "select2", + "widget", + "yii2" + ], + "time": "2017-06-08 05:53:28" + }, + { + "name": "kartik-v/yii2-widget-depdrop", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-depdrop.git", + "reference": "6918ca6f7d7be153c80f6aa9c261f9b333293e9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-depdrop/zipball/6918ca6f7d7be153c80f6aa9c261f9b333293e9c", + "reference": "6918ca6f7d7be153c80f6aa9c261f9b333293e9c", + "shasum": "" + }, + "require": { + "kartik-v/dependent-dropdown": "~1.4", + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\depdrop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Widget that enables setting up dependent dropdowns with nested dependencies (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-depdrop", + "keywords": [ + "dependent", + "dropdown", + "extension", + "form", + "jquery", + "plugin", + "widget", + "yii2" + ], + "time": "2016-01-10 17:30:48" + }, + { + "name": "kartik-v/yii2-widget-fileinput", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-fileinput.git", + "reference": "d2c8dcde1aa69ac0c4a0e3b3cfc0d79b3cc2a550" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-fileinput/zipball/d2c8dcde1aa69ac0c4a0e3b3cfc0d79b3cc2a550", + "reference": "d2c8dcde1aa69ac0c4a0e3b3cfc0d79b3cc2a550", + "shasum": "" + }, + "require": { + "kartik-v/bootstrap-fileinput": "~4.4", + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\file\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "An enhanced FileInput widget for Bootstrap 3.x with file preview, multiple selection, and more features (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-fileinput", + "keywords": [ + "extension", + "file", + "form", + "input", + "jquery", + "plugin", + "upload", + "widget", + "yii2" + ], + "time": "2017-05-25 20:12:30" + }, + { + "name": "kartik-v/yii2-widget-growl", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-growl.git", + "reference": "c79abaa47e9103e93345cd1eca7bc75e17e9a92e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-growl/zipball/c79abaa47e9103e93345cd1eca7bc75e17e9a92e", + "reference": "c79abaa47e9103e93345cd1eca7bc75e17e9a92e", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\growl\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A widget to generate growl based notifications using bootstrap-growl plugin (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-growl", + "keywords": [ + "alert", + "bootstrap", + "extension", + "growl", + "jquery", + "notification", + "plugin", + "widget", + "yii2" + ], + "time": "2015-05-03 08:23:04" + }, + { + "name": "kartik-v/yii2-widget-rangeinput", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-rangeinput.git", + "reference": "ad82cf956b95555d39dbb534587c7827b1dc7bdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-rangeinput/zipball/ad82cf956b95555d39dbb534587c7827b1dc7bdf", + "reference": "ad82cf956b95555d39dbb534587c7827b1dc7bdf", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\range\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "An enhanced Yii 2 widget encapsulating the HTML 5 range input (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-rangeinput", + "keywords": [ + "HTML5", + "extension", + "form", + "input", + "jquery", + "plugin", + "range", + "widget", + "yii2" + ], + "time": "2015-11-22 06:52:44" + }, + { + "name": "kartik-v/yii2-widget-rating", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-rating.git", + "reference": "69b192bc2b26a435618e17eed7c56294ef805fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-rating/zipball/69b192bc2b26a435618e17eed7c56294ef805fab", + "reference": "69b192bc2b26a435618e17eed7c56294ef805fab", + "shasum": "" + }, + "require": { + "kartik-v/bootstrap-star-rating": "*", + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\rating\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A Yii2 widget for the simple yet powerful bootstrap-star-rating plugin with fractional rating support (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-rating", + "keywords": [ + "Rating", + "bootstrap", + "extension", + "form", + "input", + "jquery", + "plugin", + "star", + "widget", + "yii2" + ], + "time": "2016-02-17 19:13:26" + }, + { + "name": "kartik-v/yii2-widget-select2", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-select2.git", + "reference": "a129c6663078fe0ad14cde2a5d0afd071c7608e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-select2/zipball/a129c6663078fe0ad14cde2a5d0afd071c7608e9", + "reference": "a129c6663078fe0ad14cde2a5d0afd071c7608e9", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\select2\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the Select2 jQuery plugin (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-select2", + "keywords": [ + "dropdown", + "extension", + "form", + "jquery", + "plugin", + "select2", + "widget", + "yii2" + ], + "time": "2017-08-07 18:12:36" + }, + { + "name": "kartik-v/yii2-widget-sidenav", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-sidenav.git", + "reference": "02ee4f142d7dfbb316f878e538cc7b946f4502d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-sidenav/zipball/02ee4f142d7dfbb316f878e538cc7b946f4502d2", + "reference": "02ee4f142d7dfbb316f878e538cc7b946f4502d2", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\sidenav\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "An enhanced side navigation menu styled for bootstrap (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-sidenav", + "keywords": [ + "bootstrap", + "extension", + "jquery", + "menu", + "navigation", + "plugin", + "sidenav", + "widget", + "yii2" + ], + "time": "2014-11-09 08:07:23" + }, + { + "name": "kartik-v/yii2-widget-spinner", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-spinner.git", + "reference": "3132ba14d58e0564d17f3b846b04c42aa72bdde3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-spinner/zipball/3132ba14d58e0564d17f3b846b04c42aa72bdde3", + "reference": "3132ba14d58e0564d17f3b846b04c42aa72bdde3", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\spinner\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A widget to render animated CSS3 loading spinners with VML fallback for IE (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-spinner", + "keywords": [ + "CSS3", + "extension", + "jquery", + "loading", + "plugin", + "spinner", + "widget", + "yii2" + ], + "time": "2014-11-09 05:02:05" + }, + { + "name": "kartik-v/yii2-widget-switchinput", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-switchinput.git", + "reference": "7d8ee999d79bcdc1601da5cd59439ac7eb1f5ea6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-switchinput/zipball/7d8ee999d79bcdc1601da5cd59439ac7eb1f5ea6", + "reference": "7d8ee999d79bcdc1601da5cd59439ac7eb1f5ea6", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\switchinput\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A Yii2 wrapper widget for the Bootstrap Switch plugin to use checkboxes & radios as toggle switchinputes (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-switchinput", + "keywords": [ + "bootstrap", + "extension", + "form", + "input", + "jquery", + "plugin", + "switchinput", + "toggle", + "widget", + "yii2" + ], + "time": "2016-01-10 16:47:35" + }, + { + "name": "kartik-v/yii2-widget-timepicker", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-timepicker.git", + "reference": "203aeb9123e7fa9a3c584d206793d82abe96d469" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-timepicker/zipball/203aeb9123e7fa9a3c584d206793d82abe96d469", + "reference": "203aeb9123e7fa9a3c584d206793d82abe96d469", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "kartik\\time\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the bootstrap timepicker plugin (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-timepicker", + "keywords": [ + "bootstrap", + "extension", + "form", + "jquery", + "picker", + "plugin", + "time", + "widget", + "yii2" + ], + "time": "2017-01-08 06:36:24" + }, + { + "name": "kartik-v/yii2-widget-touchspin", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-touchspin.git", + "reference": "afc56f68d87c65c9659b76ac82ae4ae8160634c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-touchspin/zipball/afc56f68d87c65c9659b76ac82ae4ae8160634c6", + "reference": "afc56f68d87c65c9659b76ac82ae4ae8160634c6", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\touchspin\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "A Yii2 wrapper widget for the Bootstrap Switch plugin to use checkboxes & radios as toggle touchspines (sub repo split from yii2-widgets)", + "homepage": "https://github.com/kartik-v/yii2-widget-touchspin", + "keywords": [ + "bootstrap", + "extension", + "form", + "input", + "jquery", + "plugin", + "spinner", + "touch", + "widget", + "yii2" + ], + "time": "2016-01-10 17:10:39" + }, + { + "name": "kartik-v/yii2-widget-typeahead", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widget-typeahead.git", + "reference": "e369cd55cb2fb9d3a82b57ea6c9a62d69f14fb94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widget-typeahead/zipball/e369cd55cb2fb9d3a82b57ea6c9a62d69f14fb94", + "reference": "e369cd55cb2fb9d3a82b57ea6c9a62d69f14fb94", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "~1.7" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\typeahead\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Enhanced Yii2 wrapper for the Twitter Typeahead plugin (sub repo split from yii2-widgets).", + "homepage": "https://github.com/kartik-v/yii2-widget-typeahead", + "keywords": [ + "dropdown", + "extension", + "form", + "jquery", + "plugin", + "typeahead", + "widget", + "yii2" + ], + "time": "2015-06-28 18:05:41" + }, + { + "name": "kartik-v/yii2-widgets", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/kartik-v/yii2-widgets.git", + "reference": "f435d5e96c4848844bdfa9cee58249ccfb3e2dd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kartik-v/yii2-widgets/zipball/f435d5e96c4848844bdfa9cee58249ccfb3e2dd2", + "reference": "f435d5e96c4848844bdfa9cee58249ccfb3e2dd2", + "shasum": "" + }, + "require": { + "kartik-v/yii2-krajee-base": "*", + "kartik-v/yii2-widget-activeform": "*", + "kartik-v/yii2-widget-affix": "*", + "kartik-v/yii2-widget-alert": "*", + "kartik-v/yii2-widget-colorinput": "*", + "kartik-v/yii2-widget-datepicker": "*", + "kartik-v/yii2-widget-datetimepicker": "*", + "kartik-v/yii2-widget-depdrop": "*", + "kartik-v/yii2-widget-fileinput": "*", + "kartik-v/yii2-widget-growl": "*", + "kartik-v/yii2-widget-rangeinput": "*", + "kartik-v/yii2-widget-rating": "*", + "kartik-v/yii2-widget-select2": "*", + "kartik-v/yii2-widget-sidenav": "*", + "kartik-v/yii2-widget-spinner": "*", + "kartik-v/yii2-widget-switchinput": "*", + "kartik-v/yii2-widget-timepicker": "*", + "kartik-v/yii2-widget-touchspin": "*", + "kartik-v/yii2-widget-typeahead": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "kartik\\widgets\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Kartik Visweswaran", + "email": "kartikv2@gmail.com", + "homepage": "http://www.krajee.com/" + } + ], + "description": "Collection of useful widgets for Yii Framework 2.0 extending functionalities for Bootstrap", + "homepage": "https://github.com/kartik-v/yii2-widgets", + "keywords": [ + "extension", + "form", + "widget", + "yii2" + ], + "time": "2014-11-09 19:54:17" + }, + { + "name": "npm-asset/after", + "version": "0.8.2", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/arraybuffer.slice", + "version": "0.0.6", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/at.js", + "version": "v1.5.4", + "source": { + "type": "git", + "url": "https://github.com/ichord/At.js.git", + "reference": "801c87dc804e37f134def2055b80cbc81ac98652" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ichord/At.js/zipball/801c87dc804e37f134def2055b80cbc81ac98652", + "reference": "801c87dc804e37f134def2055b80cbc81ac98652", + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/backo2", + "version": "1.0.2", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/base64-arraybuffer", + "version": "0.1.5", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/better-assert", + "version": "1.0.2", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/callsite": "1.0.0" + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/blob", + "version": "0.0.4", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/callsite", + "version": "1.0.0", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/component-bind", + "version": "1.0.0", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/component-emitter", + "version": "1.2.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/component-inherit", + "version": "0.0.3", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/debug", + "version": "2.6.9", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/ms": "2.0.0" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/engine.io-client", + "version": "3.1.3", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.3.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/component-emitter": "1.2.1", + "npm-asset/component-inherit": "0.0.3", + "npm-asset/debug": "~2.6.9", + "npm-asset/engine.io-parser": "~2.1.1", + "npm-asset/has-cors": "1.1.0", + "npm-asset/indexof": "0.0.1", + "npm-asset/parseqs": "0.0.5", + "npm-asset/parseuri": "0.0.5", + "npm-asset/ws": "~2.3.1", + "npm-asset/xmlhttprequest-ssl": "~1.5.4", + "npm-asset/yeast": "0.1.2" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/engine.io-parser", + "version": "2.1.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.1.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/after": "0.8.2", + "npm-asset/arraybuffer.slice": "0.0.6", + "npm-asset/base64-arraybuffer": "0.1.5", + "npm-asset/blob": "0.0.4", + "npm-asset/has-binary2": "~1.0.2" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/has-binary2", + "version": "1.0.2", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/isarray": "2.0.1" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/has-cors", + "version": "1.1.0", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/indexof", + "version": "0.0.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/isarray", + "version": "2.0.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/ms", + "version": "2.0.0", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/object-component", + "version": "0.0.3", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/parseqs", + "version": "0.0.5", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/better-assert": "~1.0.0" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/parseuri", + "version": "0.0.5", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/better-assert": "~1.0.0" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/safe-buffer", + "version": "5.0.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/socket.io-client", + "version": "2.0.4", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/backo2": "1.0.2", + "npm-asset/base64-arraybuffer": "0.1.5", + "npm-asset/component-bind": "1.0.0", + "npm-asset/component-emitter": "1.2.1", + "npm-asset/debug": "~2.6.4", + "npm-asset/engine.io-client": "~3.1.0", + "npm-asset/has-cors": "1.1.0", + "npm-asset/indexof": "0.0.1", + "npm-asset/object-component": "0.0.3", + "npm-asset/parseqs": "0.0.5", + "npm-asset/parseuri": "0.0.5", + "npm-asset/socket.io-parser": "~3.1.1", + "npm-asset/to-array": "0.1.4" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/socket.io-parser", + "version": "3.1.2", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/component-emitter": "1.2.1", + "npm-asset/debug": "~2.6.4", + "npm-asset/has-binary2": "~1.0.2", + "npm-asset/isarray": "2.0.1" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/to-array", + "version": "0.1.4", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/ultron", + "version": "1.1.0", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/ws", + "version": "2.3.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", + "reference": null, + "shasum": null + }, + "require": { + "npm-asset/safe-buffer": "~5.0.1", + "npm-asset/ultron": "~1.1.0" + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "npm-asset/xmlhttprequest-ssl", + "version": "1.5.4", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset" + }, + { + "name": "npm-asset/yeast", + "version": "0.1.2", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "reference": null, + "shasum": null + }, + "type": "npm-asset", + "license": [ + "MIT" + ] + }, + { + "name": "nqxcode/zendsearch", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/nqxcode/ZendSearch.git", + "reference": "8ad687b36ad4dd1b8007f0812e5cf73d360cb858" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nqxcode/ZendSearch/zipball/8ad687b36ad4dd1b8007f0812e5cf73d360cb858", + "reference": "8ad687b36ad4dd1b8007f0812e5cf73d360cb858", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-stdlib": "2.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "ZendSearch": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "a general purpose text search engine written entirely in PHP 5", + "homepage": "http://packages.zendframework.com/", + "keywords": [ + "lucene", + "zf2" + ], + "time": "2015-06-12 14:16:08" + }, + { + "name": "phpoffice/phpexcel", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PHPExcel.git", + "reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/372c7cbb695a6f6f1e62649381aeaa37e7e70b32", + "reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "ext-xmlwriter": "*", + "php": ">=5.2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "PHPExcel": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "http://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker" + }, + { + "name": "Franck Lefevre", + "homepage": "http://blog.rootslabs.net" + }, + { + "name": "Erik Tilt" + } + ], + "description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "http://phpexcel.codeplex.com", + "keywords": [ + "OpenXML", + "excel", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "time": "2015-05-01 07:00:55" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14 16:28:37" + }, + { + "name": "raoul2000/yii2-jcrop-widget", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/raoul2000/yii2-jcrop-widget.git", + "reference": "1a858a0cd0c3adeb5442017e098b182f2016a9b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/raoul2000/yii2-jcrop-widget/zipball/1a858a0cd0c3adeb5442017e098b182f2016a9b2", + "reference": "1a858a0cd0c3adeb5442017e098b182f2016a9b2", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "raoul2000\\jcrop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD 3-Clause" + ], + "authors": [ + { + "name": "Raoul", + "email": "raoul.boulard@gmail.com" + } + ], + "description": "This extension is a wrapper for the jQuery Image Cropping Plugin (jcrop)", + "homepage": "https://github.com/raoul2000/yii2-jcrop-widget", + "keywords": [ + "crop", + "extension", + "image", + "jquery", + "yii", + "yii2" + ], + "time": "2014-07-30 15:40:38" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517", + "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2017-05-01 15:54:03" + }, + { + "name": "symfony/process", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "fdf89e57a723a29baf536e288d6e232c059697b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/fdf89e57a723a29baf536e288d6e232c059697b1", + "reference": "fdf89e57a723a29baf536e288d6e232c059697b1", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "xj/yii2-jplayer-widget", + "version": "2.7.0.3", + "source": { + "type": "git", + "url": "https://github.com/xjflyttp/yii2-jplayer-widget.git", + "reference": "89f7fba20b0d868ed981a8130acf9e3c9bb28af9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/xjflyttp/yii2-jplayer-widget/zipball/89f7fba20b0d868ed981a8130acf9e3c9bb28af9", + "reference": "89f7fba20b0d868ed981a8130acf9e3c9bb28af9", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "xj\\jplayer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "xjflyttp", + "email": "xjflyttp@gmail.com" + } + ], + "description": "yii2-jplayer-widget", + "time": "2015-07-01 04:15:08" + }, + { + "name": "yiisoft/yii2", + "version": "2.0.12", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-framework.git", + "reference": "70acbecc75cb26b6cd66d16be0b06e4b73db190d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/70acbecc75cb26b6cd66d16be0b06e4b73db190d", + "reference": "70acbecc75cb26b6cd66d16be0b06e4b73db190d", + "shasum": "" + }, + "require": { + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/jquery.inputmask": "~3.2.2 | ~3.3.5", + "bower-asset/punycode": "1.3.*", + "bower-asset/yii2-pjax": "~2.0.1", + "cebe/markdown": "~1.0.0 | ~1.1.0", + "ext-ctype": "*", + "ext-mbstring": "*", + "ezyang/htmlpurifier": "~4.6", + "lib-pcre": "*", + "php": ">=5.4.0", + "yiisoft/yii2-composer": "~2.0.4" + }, + "bin": [ + "yii" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu", + "role": "Core framework development" + } + ], + "description": "Yii PHP Framework Version 2", + "homepage": "http://www.yiiframework.com/", + "keywords": [ + "framework", + "yii2" + ], + "time": "2017-06-05 14:33:41" + }, + { + "name": "yiisoft/yii2-authclient", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-authclient.git", + "reference": "96dd47c762749991a57d8c343b1a1ecfadf9c4c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-authclient/zipball/96dd47c762749991a57d8c343b1a1ecfadf9c4c8", + "reference": "96dd47c762749991a57d8c343b1a1ecfadf9c4c8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "yiisoft/yii2": ">=2.0.4" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\authclient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "description": "External authentication via OAuth and OpenID for the Yii framework", + "keywords": [ + "OpenId", + "auth", + "oauth", + "yii2" + ], + "time": "2016-07-08 13:58:12" + }, + { + "name": "yiisoft/yii2-bootstrap", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-bootstrap.git", + "reference": "02a54d868343ed11d02f0f0f8cbbecb590e0cb3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/02a54d868343ed11d02f0f0f8cbbecb590e0cb3f", + "reference": "02a54d868343ed11d02f0f0f8cbbecb590e0cb3f", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*", + "yiisoft/yii2": "~2.0.6" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\bootstrap\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The Twitter Bootstrap extension for the Yii framework", + "keywords": [ + "bootstrap", + "yii2" + ], + "time": "2017-10-09 19:48:22" + }, + { + "name": "yiisoft/yii2-composer", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-composer.git", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "reference": "3f4923c2bde6caf3f5b88cc22fdd5770f52f8df2", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "yii\\composer\\Plugin", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\composer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The composer plugin for Yii extension installer", + "keywords": [ + "composer", + "extension installer", + "yii2" + ], + "time": "2016-12-20 13:26:02" + }, + { + "name": "yiisoft/yii2-httpclient", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-httpclient.git", + "reference": "720e3c9bdda260abffe61babfe39b91c4308ac4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-httpclient/zipball/720e3c9bdda260abffe61babfe39b91c4308ac4c", + "reference": "720e3c9bdda260abffe61babfe39b91c4308ac4c", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.0" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\httpclient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "description": "HTTP client extension for the Yii framework", + "keywords": [ + "curl", + "http", + "httpclient", + "yii2" + ], + "time": "2017-06-23 09:36:13" + }, + { + "name": "yiisoft/yii2-imagine", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-imagine.git", + "reference": "68ed3d96cd1895035cef3c0e9ce9cf6c2c0c7f1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-imagine/zipball/68ed3d96cd1895035cef3c0e9ce9cf6c2c0c7f1c", + "reference": "68ed3d96cd1895035cef3c0e9ce9cf6c2c0c7f1c", + "shasum": "" + }, + "require": { + "imagine/imagine": "0.5.*", + "yiisoft/yii2": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\imagine\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Antonio Ramirez", + "email": "amigo.cobos@gmail.com" + } + ], + "description": "The Imagine integration for the Yii framework", + "keywords": [ + "helper", + "image", + "imagine", + "yii2" + ], + "time": "2016-09-04 15:14:08" + }, + { + "name": "yiisoft/yii2-jui", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-jui.git", + "reference": "843a2160cfe30f5c8b0cb2e9dbd8ee6bcf97e71d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-jui/zipball/843a2160cfe30f5c8b0cb2e9dbd8ee6bcf97e71d", + "reference": "843a2160cfe30f5c8b0cb2e9dbd8ee6bcf97e71d", + "shasum": "" + }, + "require": { + "bower-asset/jquery-ui": "1.11.*@stable", + "yiisoft/yii2": ">=2.0.4" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + }, + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + }, + "autoload": { + "psr-4": { + "yii\\jui\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The Jquery UI extension for the Yii framework", + "keywords": [ + "jQuery UI", + "yii2" + ], + "time": "2016-07-22 22:26:59" + }, + { + "name": "yiisoft/yii2-queue", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-queue.git", + "reference": "30647cc5710480b7ef600c33d1ae07f657daaaa3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-queue/zipball/30647cc5710480b7ef600c33d1ae07f657daaaa3", + "reference": "30647cc5710480b7ef600c33d1ae07f657daaaa3", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/process": "*", + "yiisoft/yii2": "~2.0.10" + }, + "require-dev": { + "jeremeamia/superclosure": "*", + "pda/pheanstalk": "*", + "php-amqplib/php-amqplib": "*", + "phpunit/phpunit": "~4.4", + "yiisoft/yii2-debug": "*", + "yiisoft/yii2-gii": "*", + "yiisoft/yii2-redis": "*" + }, + "suggest": { + "ext-gearman": "Need for Gearman queue.", + "ext-pcntl": "Need for process signals.", + "pda/pheanstalk": "Need for Beanstalk queue.", + "php-amqplib/php-amqplib": "Need for AMQP queue.", + "yiisoft/yii2-redis": "Need for Redis queue." + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\queue\\": "src", + "yii\\queue\\amqp\\": "src/drivers/amqp", + "yii\\queue\\beanstalk\\": "src/drivers/beanstalk", + "yii\\queue\\db\\": "src/drivers/db", + "yii\\queue\\file\\": "src/drivers/file", + "yii\\queue\\gearman\\": "src/drivers/gearman", + "yii\\queue\\redis\\": "src/drivers/redis", + "yii\\queue\\sync\\": "src/drivers/sync" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Roman Zhuravlev", + "email": "zhuravljov@gmail.com" + } + ], + "description": "Yii2 Queue Extension which supported DB, Redis, RabbitMQ, Beanstalk and Gearman", + "keywords": [ + "async", + "beanstalk", + "db", + "gearman", + "gii", + "queue", + "rabbitmq", + "redis", + "yii" + ], + "time": "2017-07-15 15:53:28" + }, + { + "name": "yiisoft/yii2-redis", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-redis.git", + "reference": "5dc55d5187923219e9db86d149d56acf1f5a6ee8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-redis/zipball/5dc55d5187923219e9db86d149d56acf1f5a6ee8", + "reference": "5dc55d5187923219e9db86d149d56acf1f5a6ee8", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.11" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" + } + ], + "description": "Redis Cache, Session and ActiveRecord for the Yii framework", + "keywords": [ + "active-record", + "cache", + "redis", + "session", + "yii2" + ], + "time": "2017-04-05 13:42:11" + }, + { + "name": "yiisoft/yii2-swiftmailer", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-swiftmailer.git", + "reference": "8a03a62cbcb82e7697d3002eb43a8d2637f566ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-swiftmailer/zipball/8a03a62cbcb82e7697d3002eb43a8d2637f566ec", + "reference": "8a03a62cbcb82e7697d3002eb43a8d2637f566ec", + "shasum": "" + }, + "require": { + "swiftmailer/swiftmailer": "~5.0", + "yiisoft/yii2": "~2.0.4" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\swiftmailer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "description": "The SwiftMailer integration for the Yii framework", + "keywords": [ + "email", + "mail", + "mailer", + "swift", + "swiftmailer", + "yii2" + ], + "time": "2017-05-01 08:29:00" + }, + { + "name": "zendframework/zend-escaper", + "version": "2.5.2", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-escaper.git", + "reference": "2dcd14b61a72d8b8e27d579c6344e12c26141d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/2dcd14b61a72d8b8e27d579c6344e12c26141d4e", + "reference": "2dcd14b61a72d8b8e27d579c6344e12c26141d4e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-escaper", + "keywords": [ + "escaper", + "zf2" + ], + "time": "2016-06-30 19:48:38" + }, + { + "name": "zendframework/zend-http", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-http.git", + "reference": "78aa510c0ea64bfb2aa234f50c4f232c9531acfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/78aa510c0ea64bfb2aa234f50c4f232c9531acfa", + "reference": "78aa510c0ea64bfb2aa234f50c4f232c9531acfa", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "zendframework/zend-loader": "^2.5.1", + "zendframework/zend-stdlib": "^3.1 || ^2.7.7", + "zendframework/zend-uri": "^2.5.2", + "zendframework/zend-validator": "^2.10.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4.1 || ^5.7.15", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-config": "^3.1 || ^2.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev", + "dev-develop": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "homepage": "https://github.com/zendframework/zend-http", + "keywords": [ + "ZendFramework", + "http", + "http client", + "zend", + "zf" + ], + "time": "2017-10-13 12:06:24" + }, + { + "name": "zendframework/zend-hydrator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-hydrator.git", + "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", + "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "^2.0@dev", + "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", + "zendframework/zend-filter": "^2.6", + "zendframework/zend-inputfilter": "^2.6", + "zendframework/zend-serializer": "^2.6.1", + "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + }, + "suggest": { + "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", + "zendframework/zend-filter": "^2.6, to support naming strategy hydrator usage", + "zendframework/zend-serializer": "^2.6.1, to use the SerializableStrategy", + "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-release-1.0": "1.0-dev", + "dev-release-1.1": "1.1-dev", + "dev-master": "2.0-dev", + "dev-develop": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Hydrator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-hydrator", + "keywords": [ + "hydrator", + "zf2" + ], + "time": "2016-02-18 22:38:26" + }, + { + "name": "zendframework/zend-ldap", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-ldap.git", + "reference": "a9284a7440e17ce0ba697670bb4db1baf2340acd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-ldap/zipball/a9284a7440e17ce0ba697670bb4db1baf2340acd", + "reference": "a9284a7440e17ce0ba697670bb4db1baf2340acd", + "shasum": "" + }, + "require": { + "ext-ldap": "*", + "php": "^5.5 || ^7.0" + }, + "require-dev": { + "php-mock/php-mock-phpunit": "~0.3", + "phpunit/phpunit": "^4.6", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-config": "^2.5", + "zendframework/zend-eventmanager": "^2.6.3 || ^3.0.1", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "zendframework/zend-eventmanager": "Zend\\EventManager component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev", + "dev-develop": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Ldap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides support for LDAP operations including but not limited to binding, searching and modifying entries in an LDAP directory", + "homepage": "https://github.com/zendframework/zend-ldap", + "keywords": [ + "ldap", + "zf2" + ], + "time": "2017-03-06 20:39:12" + }, + { + "name": "zendframework/zend-loader", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-loader.git", + "reference": "c5fd2f071bde071f4363def7dea8dec7393e135c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/c5fd2f071bde071f4363def7dea8dec7393e135c", + "reference": "c5fd2f071bde071f4363def7dea8dec7393e135c", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-loader", + "keywords": [ + "loader", + "zf2" + ], + "time": "2015-06-03 14:05:47" + }, + { + "name": "zendframework/zend-stdlib", + "version": "2.7.7", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", + "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "zendframework/zend-hydrator": "~1.1" + }, + "require-dev": { + "athletic/athletic": "~0.1", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "zendframework/zend-config": "~2.5", + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-filter": "~2.5", + "zendframework/zend-inputfilter": "~2.5", + "zendframework/zend-serializer": "~2.5", + "zendframework/zend-servicemanager": "~2.5" + }, + "suggest": { + "zendframework/zend-eventmanager": "To support aggregate hydrator usage", + "zendframework/zend-filter": "To support naming strategy hydrator usage", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-release-2.7": "2.7-dev", + "dev-master": "3.0-dev", + "dev-develop": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-stdlib", + "keywords": [ + "stdlib", + "zf2" + ], + "time": "2016-04-12 21:17:31" + }, + { + "name": "zendframework/zend-uri", + "version": "2.5.2", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-uri.git", + "reference": "0bf717a239432b1a1675ae314f7c4acd742749ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/0bf717a239432b1a1675ae314f7c4acd742749ed", + "reference": "0bf717a239432b1a1675ae314f7c4acd742749ed", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "zendframework/zend-escaper": "^2.5", + "zendframework/zend-validator": "^2.5" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "a component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "homepage": "https://github.com/zendframework/zend-uri", + "keywords": [ + "uri", + "zf2" + ], + "time": "2016-02-17 22:38:51" + }, + { + "name": "zendframework/zend-validator", + "version": "2.10.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-validator.git", + "reference": "010084ddbd33299bf51ea6f0e07f8f4e8bd832a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/010084ddbd33299bf51ea6f0e07f8f4e8bd832a8", + "reference": "010084ddbd33299bf51ea6f0e07f8f4e8bd832a8", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^2.7.6 || ^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "zendframework/zend-cache": "^2.6.1", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-config": "^2.6", + "zendframework/zend-db": "^2.7", + "zendframework/zend-filter": "^2.6", + "zendframework/zend-http": "^2.5.4", + "zendframework/zend-i18n": "^2.6", + "zendframework/zend-math": "^2.6", + "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", + "zendframework/zend-session": "^2.8", + "zendframework/zend-uri": "^2.5" + }, + "suggest": { + "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", + "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", + "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", + "zendframework/zend-i18n-resources": "Translations of validator messages", + "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", + "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10-dev", + "dev-develop": "2.11-dev" + }, + "zf": { + "component": "Zend\\Validator", + "config-provider": "Zend\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Zend\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a set of commonly needed validators", + "homepage": "https://github.com/zendframework/zend-validator", + "keywords": [ + "validator", + "zf2" + ], + "time": "2017-08-22 14:19:23" + } + ], + "packages-dev": [ + { + "name": "behat/gherkin", + "version": "v4.4.5", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2016-10-30 11:50:56" + }, + { + "name": "bower-asset/typeahead.js", + "version": "v0.11.1", + "source": { + "type": "git", + "url": "git@github.com:twitter/typeahead.js.git", + "reference": "588440f66559714280628a4f9799f0c4eb880a4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twitter/typeahead.js/zipball/588440f66559714280628a4f9799f0c4eb880a4a", + "reference": "588440f66559714280628a4f9799f0c4eb880a4a", + "shasum": null + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset" + }, + { + "name": "cebe/js-search", + "version": "0.9.3", + "source": { + "type": "git", + "url": "https://github.com/cebe/js-search.git", + "reference": "3756a8b3387f3f7e5c778b964ec681dcf110b098" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/js-search/zipball/3756a8b3387f3f7e5c778b964ec681dcf110b098", + "reference": "3756a8b3387f3f7e5c778b964ec681dcf110b098", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "bin": [ + "bin/jsindex" + ], + "type": "library", + "autoload": { + "psr-4": { + "cebe\\jssearch\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" + } + ], + "description": "A client side search engine for use on static pages.", + "time": "2016-11-22 12:11:39" + }, + { + "name": "cebe/markdown-latex", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown-latex.git", + "reference": "42eb55c6f5a8dd68a1c029755eccd73e117aaa9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown-latex/zipball/42eb55c6f5a8dd68a1c029755eccd73e117aaa9b", + "reference": "42eb55c6f5a8dd68a1c029755eccd73e117aaa9b", + "shasum": "" + }, + "require": { + "cebe/markdown": "~1.0.0", + "mikevanriel/text-to-latex": "~1.0.0", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "bin": [ + "bin/markdown-latex" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\latex\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP, that converts markdown files into latex", + "homepage": "https://github.com/cebe/markdown-latex#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "latex", + "markdown", + "markdown-extra" + ], + "time": "2017-05-19 11:16:33" + }, + { + "name": "codeception/codeception", + "version": "2.3.6", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Codeception.git", + "reference": "c3dd3b5d9e0b1ea6c2fcca52457736dc756716f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c3dd3b5d9e0b1ea6c2fcca52457736dc756716f8", + "reference": "c3dd3b5d9e0b1ea6c2fcca52457736dc756716f8", + "shasum": "" + }, + "require": { + "behat/gherkin": "~4.4.0", + "ext-json": "*", + "ext-mbstring": "*", + "facebook/webdriver": ">=1.1.3 <2.0", + "guzzlehttp/guzzle": ">=4.1.4 <7.0", + "guzzlehttp/psr7": "~1.0", + "php": ">=5.4.0 <8.0", + "phpunit/php-code-coverage": ">=2.2.4 <6.0", + "phpunit/phpunit": ">4.8.20 <7.0", + "phpunit/phpunit-mock-objects": ">2.3 <5.0", + "sebastian/comparator": ">1.1 <3.0", + "sebastian/diff": ">=1.4 <3.0", + "stecman/symfony-console-completion": "^0.7.0", + "symfony/browser-kit": ">=2.7 <4.0", + "symfony/console": ">=2.7 <4.0", + "symfony/css-selector": ">=2.7 <4.0", + "symfony/dom-crawler": ">=2.7.5 <4.0", + "symfony/event-dispatcher": ">=2.7 <4.0", + "symfony/finder": ">=2.7 <4.0", + "symfony/yaml": ">=2.7 <4.0" + }, + "require-dev": { + "codeception/specify": "~0.3", + "facebook/graph-sdk": "~5.3", + "flow/jsonpath": "~0.2", + "league/factory-muffin": "^3.0", + "league/factory-muffin-faker": "^1.0", + "monolog/monolog": "~1.8", + "pda/pheanstalk": "~3.0", + "php-amqplib/php-amqplib": "~2.4", + "predis/predis": "^1.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <4.0", + "vlucas/phpdotenv": "^2.4.0" + }, + "suggest": { + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "flow/jsonpath": "For using JSONPath in REST module", + "league/factory-muffin": "For DataFactory module", + "league/factory-muffin-faker": "For Faker support in DataFactory module", + "phpseclib/phpseclib": "for SFTP option in FTP Module", + "symfony/phpunit-bridge": "For phpunit-bridge support" + }, + "bin": [ + "codecept" + ], + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Codeception\\": "src\\Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "http://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "time": "2017-09-28 23:19:49" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "facebook/webdriver", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "eadb0b7a7c3e6578185197fd40158b08c3164c83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/eadb0b7a7c3e6578185197fd40158b08c3164c83", + "reference": "eadb0b7a7c3e6578185197fd40158b08c3164c83", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-zip": "*", + "php": "^5.5 || ~7.0", + "symfony/process": "^2.8 || ^3.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "4.6.* || ~5.0", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "^2.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-community": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for Selenium WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2017-04-28 14:54:49" + }, + { + "name": "fzaninotto/faker", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2017-08-15 16:48:10" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-06-22 18:50:49" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20 10:07:11" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20 17:10:46" + }, + { + "name": "mikevanriel/text-to-latex", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/mvriel/TextToLatex.git", + "reference": "c9f3a4d6b89f9449782455c848d5fa3dd0e216ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mvriel/TextToLatex/zipball/c9f3a4d6b89f9449782455c848d5fa3dd0e216ba", + "reference": "c9f3a4d6b89f9449782455c848d5fa3dd0e216ba", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "type": "library", + "autoload": { + "psr-0": { + "MikeVanRiel": [ + "src/", + "tests/unit/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A converter class that converts normal ASCII text to valid LaTeX", + "time": "2015-12-13 07:33:35" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-04-12 18:52:22" + }, + { + "name": "nikic/php-parser", + "version": "v0.9.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ef70767475434bdb3615b43c327e2cae17ef12eb", + "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-0": { + "PHPParser": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2014-07-23 18:24:17" + }, + { + "name": "phpdocumentor/reflection", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/Reflection.git", + "reference": "fc40c3f604ac2287eb5c314174d5109b2c699372" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/fc40c3f604ac2287eb5c314174d5109b2c699372", + "reference": "fc40c3f604ac2287eb5c314174d5109b2c699372", + "shasum": "" + }, + "require": { + "nikic/php-parser": "~0.9.4", + "php": ">=5.3.3", + "phpdocumentor/reflection-docblock": "~2.0", + "psr/log": "~1.0" + }, + "require-dev": { + "behat/behat": "~2.4", + "mockery/mockery": "~0.8", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/", + "tests/unit/", + "tests/mocks/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Reflection library to do Static Analysis for PHP Projects", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2014-11-14 11:43:04" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2016-01-25 08:17:30" + }, + { + "name": "phpspec/php-diff", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/php-diff.git", + "reference": "0464787bfa7cd13576c5a1e318709768798bec6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/php-diff/zipball/0464787bfa7cd13576c5a1e318709768798bec6a", + "reference": "0464787bfa7cd13576c5a1e318709768798bec6a", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Diff": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton" + } + ], + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", + "time": "2016-04-07 12:29:16" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2017-09-04 11:05:03" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02 07:44:40" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03 07:40:28" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26 11:10:40" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", + "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-02-27 10:12:30" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.22", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "10df877596c9906d4110b5b905313829043f2ada" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/10df877596c9906d4110b5b905313829043f2ada", + "reference": "10df877596c9906d4110b5b905313829043f2ada", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0.3|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-09-24 07:23:38" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2017-06-30 09:13:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "scrivo/highlight.php", + "version": "v8.9.1", + "source": { + "type": "git", + "url": "https://github.com/scrivo/highlight.php.git", + "reference": "d861aee53999963aed4b742cfe21181bec178f35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/d861aee53999963aed4b742cfe21181bec178f35", + "reference": "d861aee53999963aed4b742cfe21181bec178f35", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Highlight\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Server side syntax highlighter that supports over 90 languages. It's a PHP port of highlight.js", + "keywords": [ + "code", + "highlight", + "highlight.js", + "highlight.php", + "syntax" + ], + "time": "2015-12-31 20:33:22" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04 06:30:41" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29 09:50:25" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22 07:24:03" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26 07:53:53" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19 08:54:04" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18 15:18:39" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19 07:33:16" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03 07:35:21" + }, + { + "name": "stecman/symfony-console-completion", + "version": "0.7.0", + "source": { + "type": "git", + "url": "https://github.com/stecman/symfony-console-completion.git", + "reference": "5461d43e53092b3d3b9dbd9d999f2054730f4bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stecman/symfony-console-completion/zipball/5461d43e53092b3d3b9dbd9d999f2054730f4bbb", + "reference": "5461d43e53092b3d3b9dbd9d999f2054730f4bbb", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/console": "~2.3 || ~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stecman\\Component\\Symfony\\Console\\BashCompletion\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Holdaway", + "email": "stephen@stecman.co.nz" + } + ], + "description": "Automatic BASH completion for Symfony Console Component based applications.", + "time": "2016-02-24 05:08:54" + }, + { + "name": "symfony/browser-kit", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "317d5bdf0127f06db7ea294186132b4f5b036839" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/317d5bdf0127f06db7ea294186132b4f5b036839", + "reference": "317d5bdf0127f06db7ea294186132b4f5b036839", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/console", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/116bc56e45a8e5572e51eb43ab58c769a352366c", + "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/css-selector", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "07447650225ca9223bd5c97180fe7c8267f7d332" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/07447650225ca9223bd5c97180fe7c8267f7d332", + "reference": "07447650225ca9223bd5c97180fe7c8267f7d332", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/debug", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd", + "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/40dafd42d5dad7fe5ad4e958413d92a207522ac1", + "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "d7ba037e4b8221956ab1e221c73c9e27e05dd423" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d7ba037e4b8221956ab1e221c73c9e27e05dd423", + "reference": "d7ba037e4b8221956ab1e221c73c9e27e05dd423", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/finder", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "773e19a491d97926f236942484cb541560ce862d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/773e19a491d97926f236942484cb541560ce862d", + "reference": "773e19a491d97926f236942484cb541560ce862d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2017-10-02 06:42:24" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-14 15:44:48" + }, + { + "name": "symfony/yaml", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", + "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-10-05 14:43:42" + }, + { + "name": "yiisoft/yii2-apidoc", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-apidoc.git", + "reference": "15ccf08af29896f0bbd8f7ab0c2a0a808e38cce8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-apidoc/zipball/15ccf08af29896f0bbd8f7ab0c2a0a808e38cce8", + "reference": "15ccf08af29896f0bbd8f7ab0c2a0a808e38cce8", + "shasum": "" + }, + "require": { + "cebe/js-search": "~0.9.3", + "cebe/markdown": "~1.0.0 | ~1.1.0", + "cebe/markdown-latex": "~1.0", + "nikic/php-parser": "0.9.*", + "phpdocumentor/reflection": "^1.0.3", + "phpdocumentor/reflection-docblock": "^2.0.1", + "scrivo/highlight.php": "~8.0", + "yiisoft/yii2": "~2.0.4", + "yiisoft/yii2-bootstrap": "~2.0.0" + }, + "bin": [ + "apidoc" + ], + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + }, + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + }, + "autoload": { + "psr-4": { + "yii\\apidoc\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" + } + ], + "description": "API Documentation generator for the Yii framework 2.0", + "keywords": [ + "api", + "apidoc", + "documentation", + "phpdoc", + "yii2" + ], + "time": "2016-11-22 14:21:51" + }, + { + "name": "yiisoft/yii2-debug", + "version": "2.0.12", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-debug.git", + "reference": "93082f46d3568b4431a26f264e0d16a12c42bd50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/93082f46d3568b4431a26f264e0d16a12c42bd50", + "reference": "93082f46d3568b4431a26f264e0d16a12c42bd50", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "~2.0.11", + "yiisoft/yii2-bootstrap": "~2.0.0" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The debugger extension for the Yii framework", + "keywords": [ + "debug", + "debugger", + "yii2" + ], + "time": "2017-10-09 20:30:01" + }, + { + "name": "yiisoft/yii2-faker", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-faker.git", + "reference": "b88ca69ee226a3610b2c26c026c3203d7ac50f6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/b88ca69ee226a3610b2c26c026c3203d7ac50f6c", + "reference": "b88ca69ee226a3610b2c26c026c3203d7ac50f6c", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "*", + "yiisoft/yii2": "*" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\faker\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "description": "Fixture generator. The Faker integration for the Yii framework.", + "keywords": [ + "Fixture", + "faker", + "yii2" + ], + "time": "2015-03-01 06:22:44" + }, + { + "name": "yiisoft/yii2-gii", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-gii.git", + "reference": "1bd6df6804ca077ec022587905a0d43eb286f507" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-gii/zipball/1bd6df6804ca077ec022587905a0d43eb286f507", + "reference": "1bd6df6804ca077ec022587905a0d43eb286f507", + "shasum": "" + }, + "require": { + "bower-asset/typeahead.js": "0.10.* | ~0.11.0", + "phpspec/php-diff": ">=1.0.2", + "yiisoft/yii2": ">=2.0.4", + "yiisoft/yii2-bootstrap": "~2.0" + }, + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + }, + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + }, + "autoload": { + "psr-4": { + "yii\\gii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The Gii extension for the Yii framework", + "keywords": [ + "code generator", + "gii", + "yii2" + ], + "time": "2016-03-18 14:09:46" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "bower-asset/select2-bootstrap-theme": 10 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.6" + } +} diff --git a/protected/config/.gitignore b/protected/config/.gitignore index 5acdb015dc..11a91c70fc 100644 --- a/protected/config/.gitignore +++ b/protected/config/.gitignore @@ -1 +1,4 @@ /dynamic.php +/common.php +/console.php +/web.php diff --git a/protected/config/common.php b/protected/config/common.php index b62512838d..fa5523ec2a 100644 --- a/protected/config/common.php +++ b/protected/config/common.php @@ -1,4 +1,9 @@ $this->id]) as $containerSetting) { - $containerSetting->delete(); - } - - foreach (\humhub\models\Setting::findAll(['module_id' => $this->id]) as $containerSetting) { - $containerSetting->delete(); - } - - foreach (\humhub\modules\user\models\Module::findAll(['module_id' => $this->id]) as $userModule) { - $userModule->delete(); - } - - foreach (\humhub\modules\space\models\Module::findAll(['module_id' => $this->id]) as $spaceModule) { - $spaceModule->delete(); - } - + ContentContainerSetting::deleteAll(['module_id' => $this->id]); + Setting::deleteAll(['module_id' => $this->id]); + Yii::$app->moduleManager->disable($this); } @@ -347,7 +336,7 @@ class Module extends \yii\base\Module if (is_dir($notificationDirectory)) { foreach (FileHelper::findFiles($notificationDirectory, ['recursive' => false,]) as $file) { $notificationClass = $notificationNamespace . '\\' . basename($file, '.php'); - if(is_subclass_of($notificationClass, BaseNotification::class)) { + if (is_subclass_of($notificationClass, BaseNotification::class)) { $notifications[] = $notificationClass; } } diff --git a/protected/humhub/components/UrlManager.php b/protected/humhub/components/UrlManager.php new file mode 100644 index 0000000000..9b9842deed --- /dev/null +++ b/protected/humhub/components/UrlManager.php @@ -0,0 +1,42 @@ +contentContainerRecord->guid; + unset($params['contentContainer']); + } + + if (isset($params['container']) && $params['container'] instanceof ContentContainerActiveRecord) { + $params['cguid'] = $params['container']->contentContainerRecord->guid; + unset($params['container']); + } + + return parent::createUrl($params); + } + +} diff --git a/protected/humhub/components/queue/ActiveJob.php b/protected/humhub/components/queue/ActiveJob.php index aaa832a89f..93701aa496 100644 --- a/protected/humhub/components/queue/ActiveJob.php +++ b/protected/humhub/components/queue/ActiveJob.php @@ -9,18 +9,16 @@ namespace humhub\components\queue; use yii\base\Object; -use zhuravljov\yii\queue\Job; /** * ActiveJob - * + * + * @see \humhub\modules\queue\ActiveJob + * @deprecated since version 1.3 * @since 1.2 * @author Luke */ -abstract class ActiveJob extends Object implements Job +abstract class ActiveJob extends \humhub\modules\queue\ActiveJob { - /** - * Runs this job - */ - abstract public function run(); + } diff --git a/protected/humhub/components/queue/driver/Instant.php b/protected/humhub/components/queue/driver/Instant.php deleted file mode 100644 index 1a31a5c631..0000000000 --- a/protected/humhub/components/queue/driver/Instant.php +++ /dev/null @@ -1,47 +0,0 @@ -error; - Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue'); - }); - } - - /** - * @inheritdoc - */ - protected function sendMessage($message, $timeout) - { - $this->handleMessage($message); - } - -} diff --git a/protected/humhub/components/queue/driver/MySQL.php b/protected/humhub/components/queue/driver/MySQL.php deleted file mode 100644 index 14d9694128..0000000000 --- a/protected/humhub/components/queue/driver/MySQL.php +++ /dev/null @@ -1,44 +0,0 @@ -error; - Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue'); - }); - } - -} diff --git a/protected/humhub/components/queue/driver/Sync.php b/protected/humhub/components/queue/driver/Sync.php deleted file mode 100644 index dcdcb4ae37..0000000000 --- a/protected/humhub/components/queue/driver/Sync.php +++ /dev/null @@ -1,84 +0,0 @@ -handle) { - Yii::$app->on(Application::EVENT_AFTER_REQUEST, function () { - ob_start(); - $this->run(); - - // Important, breaks downloads - ob_end_clean(); - }); - } - - Event::on(Queue::class, Queue::EVENT_AFTER_ERROR, function(ErrorEvent $errorEvent) { - /* @var $exception \Expection */ - $exception = $errorEvent->error; - Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue'); - }); - } - - /** - * Runs all jobs from queue. - */ - public function run() - { - while (($message = array_shift($this->messages)) !== null) { - $this->handleMessage($message); - } - } - - /** - * @inheritdoc - */ - protected function sendMessage($message, $timeout) - { - if ($timeout) { - throw new NotSupportedException('Delayed work is not supported in the driver.'); - } - - $this->messages[] = $message; - } - -} diff --git a/protected/humhub/config/common.php b/protected/humhub/config/common.php index b20a5ae505..7bba463cc7 100644 --- a/protected/humhub/config/common.php +++ b/protected/humhub/config/common.php @@ -16,6 +16,10 @@ $config = [ 'basePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR, 'bootstrap' => ['log', 'humhub\components\bootstrap\ModuleAutoLoader', 'queue'], 'sourceLanguage' => 'en', + 'aliases' => [ + '@bower' => '@vendor/bower-asset', + '@npm' => '@vendor/npm-asset', + ], 'components' => [ 'moduleManager' => [ 'class' => '\humhub\components\ModuleManager' @@ -120,7 +124,7 @@ $config = [ 'dsn' => 'mysql:host=localhost;dbname=humhub', 'username' => '', 'password' => '', - 'charset' => 'utf8', + 'charset' => 'utf8mb4', 'enableSchemaCache' => true, 'on afterOpen' => ['humhub\libs\Helpers', 'SqlMode'], ], @@ -129,12 +133,15 @@ $config = [ 'clients' => [], ], 'queue' => [ - 'class' => 'humhub\components\queue\driver\Sync', + 'class' => 'humhub\modules\queue\driver\Sync', + ], + 'urlManager' => [ + 'class' => 'humhub\components\UrlManager', ], 'live' => [ 'class' => 'humhub\modules\live\components\Sender', 'driver' => [ - 'class' => 'humhub\modules\live\driver\Database', + 'class' => 'humhub\modules\live\driver\Poll', ], ], ], diff --git a/protected/humhub/config/web.php b/protected/humhub/config/web.php index 09b5e618af..455cdf9ed8 100644 --- a/protected/humhub/config/web.php +++ b/protected/humhub/config/web.php @@ -27,11 +27,10 @@ $config = [ 'loginUrl' => ['/user/auth/login'] ], 'errorHandler' => [ - 'errorAction' => 'error/index', + 'errorAction' => '/error/index', ], 'session' => [ 'class' => 'humhub\modules\user\components\Session', - 'sessionTable' => 'user_http_session', ], ], 'modules' => [], diff --git a/protected/humhub/docs/CHANGELOG_DEV.md b/protected/humhub/docs/CHANGELOG_DEV.md new file mode 100644 index 0000000000..8ebcaa3c91 --- /dev/null +++ b/protected/humhub/docs/CHANGELOG_DEV.md @@ -0,0 +1,26 @@ +HumHub Change Log - v1.3-dev Branch +=================================== + +1.3.0-beta.1 (Not released yet) +-------------------------------- + +- Enh: Added file search indexing +- Enh: Updated composer.json (acs-ferreira) +- Chg: Switched from Composer FXP plugin to Asset Packagist repository +- Enh: Committed composer.lock +- Enh: Refactored ContentContainer Controller +- Chg: Added ContentContainer ModuleManager, instead of individual handling (Space/User) +- Fix: Rebind LDAP connection after successful login with administrative user +- Enh: Make utf8_mb4 as default database charset +- Enh: Moved queueing into own submodule and updated to yii2/queue extension +- Enh: Added user soft deletion without contributions +- Enh: Moved user deletion into asynchronous tasks +- Enh: Improved user grid view design (Administration, User Approval, Space Members) +- Enh: Moved SyncUsers (LDAP) and session table cleanup handling into ActiveJob +- Enh: Added Push live module driver using Redis and Node.JS +- Enh: Added tooltip option to space Image widget. +- Enh: Added option ContentContainerController to restrict container type +- Enh: Ensure valid permalinks when URL rewriting is enabled +- Fix: Birthday field refactoring (@danielkesselberg) +- Enh #2811: Added option to resend invites (@danielkesselberg) + diff --git a/protected/humhub/docs/guide/README.md b/protected/humhub/docs/guide/README.md index 9305832fff..07656d8fb4 100644 --- a/protected/humhub/docs/guide/README.md +++ b/protected/humhub/docs/guide/README.md @@ -1,13 +1,13 @@ Welcome ======= -Here you will find helpful information on how to create, maintain, and make the most of an social network built using the HumHub solution. +Here you will find helpful information on how to create, maintain, and make the most of a social network built using the HumHub solution. -All documentations located on docs.humhub.org refers to the latest version of HumHub. If you're using an older version, you can find the related documentation in the -folder /protected/humhub/docs. +All documentation located on docs.humhub.org refers to the latest version of HumHub. If you're using an older version, you can find the related documentation in the folder /protected/humhub/docs. Documentations -------------- +- [Basics](basics/README.md) (Note: This directory stores basic knowledge on HumHub Features) - [Installation and Administration](admin/README.md) - [Theming](theme/README.md) - [Core and Module Development](developer/README.md) diff --git a/protected/humhub/docs/guide/admin/advanced-configuration.md b/protected/humhub/docs/guide/admin/advanced-configuration.md index 9b63e3cd01..54fcfb77e8 100644 --- a/protected/humhub/docs/guide/admin/advanced-configuration.md +++ b/protected/humhub/docs/guide/admin/advanced-configuration.md @@ -52,9 +52,30 @@ Thre previous configuration will disable pjax support on your site. Available params: - `allowedLanguages` see the [Translations Section](translations.md) -- `defaultPermissions` see [Permissions Section (TBD)]() - `enablePjax` used to disable/enable pjax support (default true) +## Overwrite default Permissions + +Default permission can be overwritten within `humhub/config/common.php` by means of the `defaultPermissions` params array. +The following example overwrites the default permission of `humhub\modules\mymodule\permissions\MyPermission` for the +given groups. + + +``` +return [ + 'params' => [ + 'defaultPermissions' => [ + 'humhub\modules\mymodule\permissions\MyPermission' => [ + \humhub\modules\user\models\User::USERGROUP_SELF => \humhub\libs\BasePermission::STATE_ALLOW, + \humhub\modules\user\models\User::USERGROUP_USER => \humhub\libs\BasePermission::STATE_ALLOW, + \humhub\modules\user\models\User::USERGROUP_FRIEND => \humhub\libs\BasePermission::STATE_ALLOW, + \humhub\modules\user\models\User::USERGROUP_GUEST => \humhub\libs\BasePermission::STATE_ALLOW, + ], + ] + ] +] +``` + # Statistics/Tracking Your tracking code can be managed under `Administration -> Settings -> Advanced -> Statistics`. diff --git a/protected/humhub/docs/guide/admin/asynchronous-tasks.md b/protected/humhub/docs/guide/admin/asynchronous-tasks.md new file mode 100644 index 0000000000..e53e877dfc --- /dev/null +++ b/protected/humhub/docs/guide/admin/asynchronous-tasks.md @@ -0,0 +1,116 @@ +Asynchronous Task Processing +============================ + +Introduction +------------ + +To provide a fast and responive user experience, extensive processes are handeld activly by background processes instead being directly executed on request. + +Some examples for such background processes are: + +- Notifications (informing the users via e-ails or mobile push notifications) +- Search index rebuilds +- File indexing + + +Queue Driver +------------ + +### Sychronous Driver + +By default this driver is used to immediately execute asychronous tasks. +It doesn't require any Worker configuration below. + +We recommend to switch to the MySQL or Redis driver on production environments. + +### MySQL Database Driver + +If you don't have Redis or any other supported queuing software (RabbitMQ, Beanstalk or Gearman) running, this is the recommended driver. +To enable this driver you need to add following block to your local configuration file (protected/config/common.php): + +``` + // ... + 'components' => [ + // ... + + 'queue' => [ + 'class' => 'humhub\components\queue\driver\MySQL', + ], + + // ... + ], + // ... + +``` + +> Note: You'll need to configure Workers (see description below). + +### Redis + +If you're already using Redis (e.g. for caching or push) we recommend this queue driver. +Please make sure you already configured Redis as described here: [Redis Configuration](redis.md). + + +To enable this driver you need to add following block to your local configuration file (protected/config/common.php): + +``` + // ... + 'components' => [ + // ... + + 'queue' => [ + 'class' => 'humhub\components\queue\driver\Redis', + ], + + // ... + ], + // ... + +``` + +> Note: You'll need to configure Workers (see description below). + + +Workers +------ + +### Cronjob + +You can start workers using cron by executing the queue/run command. It works as long as the queues contain jobs. + +CronTab Example: + +``` +* * * * * /usr/bin/php /yii queue/run +``` + +In this case the cron will start the command every minute and execute schedulded tasks. + + +### Daemon + +You can start a worker deamon using following command: + +``` +cd protected +php yii queue/listen +``` + +***Using Supervisor (recommended)*** + +Supervisor is a process monitoring tool for Linux. It automatically starts, monitors and restarts your workers if they crash. + +Example configuration (e.g. /etc/supervisor/conf.d/humhub.conf): + +```conf +[program:humhub-workers] +process_name=%(program_name)s_%(process_num)02d +command=/usr/bin/php /protected/yii queue/listen --verbose=1 --color=0 +autostart=true +autorestart=true +user=www-data +numprocs=4 +redirect_stderr=true +stdout_logfile=/protected/runtime/logs/yii-queue-worker.log +``` + diff --git a/protected/humhub/docs/guide/admin/authentication.md b/protected/humhub/docs/guide/admin/authentication.md index e16070b1c1..9690fbcd37 100644 --- a/protected/humhub/docs/guide/admin/authentication.md +++ b/protected/humhub/docs/guide/admin/authentication.md @@ -81,14 +81,16 @@ return [ Google ------ In order to use Google OAuth you must create a **project** at -and setup its credentials at . +and set up its credentials at . -In order to enable using scopes for retrieving user attributes, you have to enable Google+ API at +In order to enable using scopes for retrieving user attributes, you should also enable Google+ API at . +Authorization callback URLs: + Add one of the following **authorization callback URLs** to your googles **Credentials** configuration: -- http:////user/auth/external?authclient=google (With clean urls enabled) -- http:////index.php?r=user%2Fauth%2Fexternal&authclient=google (Without clean urls) +- http://domain/path-to-humhub/user/auth/external?authclient=google (With clean urls enabled) +- http://domain/path-to-humhub/index.php?r=user%2Fauth%2Fexternal&authclient=google (Without clean urls) >Note: Replace **domain** and **path-to-humhub** in the mentioned redirect urls. @@ -123,7 +125,7 @@ Authorization callback URLs: - http://domain/path-to-humhub/user/auth/external (With clean urls enabled) - http://domain/path-to-humhub/index.php?r=user%2Fauth%2Fexternal (Without clean urls) -Add following block to your configuration (protected/config/common.php): +Add the following block to your configuration (protected/config/common.php): ```php return [ @@ -153,7 +155,7 @@ Microsoft Live -------------- In order to use Microsoft Live OAuth you must register your application at . -Also add a new Platform and allow following Redirect URI. +Also, add a new Platform and allow following Redirect URI. - https://domain/path-to-humhub/user/auth/external (With clean urls enabled) - https://domain/path-to-humhub/index.php (Without clean urls) @@ -208,33 +210,6 @@ return [ ]; ``` -Instagram --------- -In order to use Instagram OAuth you must register your application at . - -Add the following block to your configuration (protected/config/common.php): - -```php -return [ - // ... - 'components' => [ - // ... - 'authClientCollection' => [ - 'clients' => [ - // ... - 'instagram' => [ - 'class' => 'humhub\modules\user\authclient\Instagram', - 'clientId' => 'Your Instagram App ID here', - 'clientSecret' => 'Your Instagram App Secret here', - ], - ], - ], - // ... - ], - // ... -]; -``` - Other providers --------------- Please see [Development - Authentication](dev-authentication.md) for more information diff --git a/protected/humhub/docs/guide/admin/console.md b/protected/humhub/docs/guide/admin/console.md index 3dc635f902..713f6aafb6 100644 --- a/protected/humhub/docs/guide/admin/console.md +++ b/protected/humhub/docs/guide/admin/console.md @@ -1,5 +1,12 @@ Console ======= -TBD +Some administrative tasks can also or exclusively be executed by console. All available console commands can be displayed +by executing the following command within the `protected` directory of your humhub installation. + +``` +php yii +``` + +![Command Overview](images/commandOverview.png) diff --git a/protected/humhub/docs/guide/admin/cron-jobs.md b/protected/humhub/docs/guide/admin/cron-jobs.md new file mode 100644 index 0000000000..ef830e884c --- /dev/null +++ b/protected/humhub/docs/guide/admin/cron-jobs.md @@ -0,0 +1,71 @@ +Cron Job Setup +======= + +The following guides are meant to help you with your Cron Job setup, since those settings are highly dependent on your actual environment we can't assure those setting will work for your. + +> Note: Make sure to use the right [php cli executable](http://php.net/manual/en/features.commandline.introduction.php) for your jobs! + +### CloudLinux (CentOS) 6 +The following is a default setup for CloudLinux (CentOS) 6 and may not work for all users. + +``` +/usr/local/bin/php /home/USERNAME/public_html/WEB-DIRECTORY/protected/yii cron/hourly >/dev/null 2>&1 + +30 * * * * + +/usr/local/bin/php /home/USERNAME/public_html/WEB-DIRECTORY/protected/yii cron/daily >/dev/null 2>&1 + +00 18 * * * +``` + +### cPanel Hosted Server +The following is a default setup for cPanel Hosted Server and may not work for all users. + +``` +/usr/local/bin/php /home/USERNAME/public_html/WEB-DIRECTORY/protected/yii cron/hourly >/dev/null 2>&1 + +0,30 * * * * + +/usr/local/bin/php /home/USERNAME/public_html/WEB-DIRECTORY/protected/yii cron/daily >/dev/null 2>&1 + +00 0,12 * * * +``` + +### IIS Windows Server +Using [Schtasks](https://technet.microsoft.com/en-us/library/cc725744.aspx) would be recommended over many other options for Windows 2012 and Windows 8 users. + +`Example TBA` + +### Plesk +Refer to this [post](https://stackoverflow.com/questions/16700749/setting-up-cron-task-in-plesk-11) + +![](http://i.imgur.com/TbWEsjC.png) + +### OVH +[Follow this link](https://www.ovh.com/us/g1990.hosting_automated_taskscron)! + +Create the following files then follow the above link. + +**cronh.php** + +`` + +**crond.php** + +`` + +### Debian +Please read up on this [article](https://debian-administration.org/article/56/Command_scheduling_with_cron). + +`Example TBA` + +### Ubuntu +Please read up on this [how-to guide](https://help.ubuntu.com/community/CronHowto). + +`Example TBA` + +### Other Server(s) +`TBA` + +### *IMPORTANT NOTICE* +*This guide is subject to changes and all information provided are for example use, it is best to speak with your service providers about how best to setup your Cron Jobs with their services.* diff --git a/protected/humhub/docs/guide/admin/images/commandOverview.png b/protected/humhub/docs/guide/admin/images/commandOverview.png new file mode 100644 index 0000000000..407c572721 Binary files /dev/null and b/protected/humhub/docs/guide/admin/images/commandOverview.png differ diff --git a/protected/humhub/docs/guide/admin/installation-configuration.md b/protected/humhub/docs/guide/admin/installation-configuration.md index 1320c1866f..67f79475bd 100644 --- a/protected/humhub/docs/guide/admin/installation-configuration.md +++ b/protected/humhub/docs/guide/admin/installation-configuration.md @@ -3,23 +3,6 @@ Configuration > NOTE: Before going to production, see also the [Security Chapter](security.md) -Basic Settings -------- -Once installed, you should have a look at the basic application settings under `Administration/Settings/Basic` -for settings as: - - - Base Url - - Default language - - Time zone - - etc. - -Within this view you can also select **default spaces**, which will be automatically assigned to new members. - -Advanced Settings -------- -You may also check the advanced settings under `Administration/Settings/Advanced` and Notification settings to set -some default values for your user. - E-Mails ------- @@ -27,7 +10,8 @@ E-Mails Depending on your environment you are using, you may want to specify a local or remote SMTP Server. You can change the mail-server settings under `Administration -> Mailing -> Server Settings`. -By default the PHP Mail Transport is used. +By default, the PHP Mail Transport is used. + CronJobs @@ -42,7 +26,7 @@ Example Tab: 30 * * * * /path/to/humhub/protected/yii cron/hourly >/dev/null 2>&1 00 18 * * * /path/to/humhub/protected/yii cron/daily >/dev/null 2>&1 ``` - +> Note: For more help refer to [here](cron-jobs.md)! Url Rewriting (Optional) ------------------------ @@ -68,4 +52,4 @@ return [ Job Scheduling -------------- -TBD \ No newline at end of file +TBD diff --git a/protected/humhub/docs/guide/admin/installation.md b/protected/humhub/docs/guide/admin/installation.md index ed59793b36..ce94edf002 100644 --- a/protected/humhub/docs/guide/admin/installation.md +++ b/protected/humhub/docs/guide/admin/installation.md @@ -4,28 +4,27 @@ Installation > Note: It's also possible to install and build HumHub directly from our **Git Repository**. Please see [Developer Installation](../developer/git-installation.md) for more details. - - Database -------- - Create a MySQL Database, e.g.: ```sql -CREATE DATABASE `humhub` CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE DATABASE `humhub` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL ON `humhub`.* TO `humhub_dbuser`@localhost IDENTIFIED BY 'password_changeme'; FLUSH PRIVILEGES; ``` > Note: Do not forget to change the `humhub_dbuser` and `password_changeme` placeholders! -> Warning: Make sure to use the **utf8_general_ci** database collation! +> Note: `utf8mb4` is prefered over `utf8` since MySQL 5.5.3 please refer to the [mysql documentation](https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html) for more infos. + +> Warning: Make sure to use the **utf8mb4_unicode_ci** database collation! Download HumHub Core Files --------------------------- -The easiest way to get HumHub, is the direct download of the complete package under [https://www.humhub.org/en/download](https://www.humhub.org/en/download). +The easiest way to get HumHub, is the direct download of the complete package under [https://www.humhub.org/download](https://www.humhub.org/download). After the download completed, just extract the package into the htdocs folder of your webserver. diff --git a/protected/humhub/docs/guide/admin/push-updates.md b/protected/humhub/docs/guide/admin/push-updates.md new file mode 100644 index 0000000000..6f0074a6ca --- /dev/null +++ b/protected/humhub/docs/guide/admin/push-updates.md @@ -0,0 +1,64 @@ +Push Updates / Push Service +=========================== + +The PushService directly sends updates (e.g. new notifications) to the users using WebSockets or Long Polling techniques. + + +Prerequisites +------------- + +The PushService requires following additional installed software: + +- NodeJS +- Redis + + +PushService Installation +------------------------ + +You can install the HumHub PushService as NPM Package by entering following command: + +``` +npm install humhub-pushservice +``` + +Once the installation is finished, you need to create a configuration file: + +``` +cp config.json.dist config.json +``` + +Modify the config.json file and adjust the available settings. + + +Now you can start the PushService using following command: + +``` +node pushService.js +``` + + +HumHub - Configuration +---------------------- + +Once the PushService NodeJS application is up and running you need to add following +configuration options to the HumHub file (protected/config/common.php): + +``` + // ... + 'components' => [ + // ... + + 'live' => [ + 'driver' => [ + 'class' => \humhub\modules\live\driver\Push::class, + 'pushServiceUrl' => 'http://example.com:3000/', + 'jwtKey' => '---EnteraSuperSecretKeyToSignAuthorizationHere---' + ] + ], + + // ... + ], + // ... + +``` \ No newline at end of file diff --git a/protected/humhub/docs/guide/admin/redis.md b/protected/humhub/docs/guide/admin/redis.md new file mode 100644 index 0000000000..bb23ff5b59 --- /dev/null +++ b/protected/humhub/docs/guide/admin/redis.md @@ -0,0 +1,47 @@ +Redis +===== + +We recommend installing an additional Redis server which can act as a caching, push service and job queuing service for HumHub. + + +Basic Configuration +------------------ + +To enable Redis, you have to add following block to your local configuration file (protected/config/common.php): + +``` + // ... + 'components' => [ + // ... + + 'redis' => [ + 'class' => 'yii\redis\Connection', + 'hostname' => 'localhost', + 'port' => 6379, + 'database' => 0, + ], + + // ... + ], + // ... + +``` + +Caching +------- + +Once Redis is configured, you can also select it as a caching service: Administration -> Settings -> Advanced -> Caching. + + +Queuing of Asychronous Tasks +---------------------------- + +See [Asychronous Tasks](asychornous-tasks.md) for further information. + + +Push Updates +------------ + +See [Push Updates](push-updates.md) for further information. + + diff --git a/protected/humhub/docs/guide/admin/requirements.md b/protected/humhub/docs/guide/admin/requirements.md index 5cb618b280..b4e8811758 100644 --- a/protected/humhub/docs/guide/admin/requirements.md +++ b/protected/humhub/docs/guide/admin/requirements.md @@ -1,11 +1,9 @@ Requirements ============ - > Note: All vital requirements will be also checked during the web installer. Server Requirements ------------------- - * Shell access (e.g. ssh) to server * PHP 5.6 or later * MySQL (5.1 or later) or MariaDB with InnoDB storage engine installed @@ -13,27 +11,23 @@ Server Requirements * A minimum 64 MB of memory allocated to PHP * A minimum of 50 MB of database space - Required PHP Extensions ----------------------- -* PHP CUrl Extension (w/ SSL Support) -* PHP Multibyte String Support -* PHP PDO MySQL Extension (http://www.php.net/manual/en/ref.pdo-mysql.php) -* PHP Zip Extension (http://php.net/manual/en/book.zip.php) -* PHP EXIF Extension (http://php.net/manual/en/book.exif.php) -* PHP INTL Extension (http://php.net/manual/en/intro.intl.php) -* PHP FileInfo Extension (http://php.net/manual/en/fileinfo.installation.php) - +* PHP CUrl Extension (w/ SSL Support) +* PHP Multibyte String Support +* PHP PDO MySQL Extension (https://secure.php.net/manual/en/ref.pdo-mysql.php) +* PHP Zip Extension (https://secure.php.net/manual/en/book.zip.php) +* PHP EXIF Extension (https://secure.php.net/manual/en/book.exif.php) +* PHP INTL Extension (https://secure.php.net/manual/en/intro.intl.php) +* PHP FileInfo Extension (https://secure.php.net/manual/en/fileinfo.installation.php) Optional PHP Extensions ----------------------- - * ImageMagick * PHP LDAP Support * PHP APC * PHP Memcached - Database -------- The database user you tell HumHub to connect with must have the following privileges: diff --git a/protected/humhub/docs/guide/admin/search.md b/protected/humhub/docs/guide/admin/search.md index df04a444d8..552f53813d 100644 --- a/protected/humhub/docs/guide/admin/search.md +++ b/protected/humhub/docs/guide/admin/search.md @@ -18,7 +18,7 @@ cd /path/to/humhub/protected php yii search/rebuild ``` -or by means of [grunt](../developer/core-build.md): +or by means of [grunt](../developer/build.md): ``` grunt build-search @@ -27,7 +27,7 @@ grunt build-search Zend Lucence Engine -------------------- -By default HumHub is using a *Lucence* Index (Zend Lucence) to store search data. +By default, HumHub is using a *Lucence* Index (Zend Lucence) to store search data. Default database folder: `/protected/runtime/searchdb/` @@ -43,32 +43,4 @@ You can modify the default search directory in the [configuration](advanced-conf ] // ... ]; -``` - -### Limitations - -The Zend Lucence Engine runs inside the PHP process and is limited by the -settings of the PHP environment in terms of memory usage and execution time. - -By default Zend Lucence Engine sets a limit on the number of terms in a search query, -which also results in a limitation of the number of items a search term can match. - -For the space search this must be set at least as high as the number of spaces. -In general the limit depends on the number of items a search term can match so it -highly depends on the content. To be sure all searches work you can set it higher than the -number of spaces/users/content you have. - -It can be set to 0 for no limitation, but that may result in search queries -to fail caused by high memory usage. - -You can [configure](advanced-configuration.md) the limit by setting `searchItemLimit` on the `search` application component: - -```php -return [ - 'components' => [ - 'search' => [ - 'searchItemLimit' => 10000, - ], - ], -]; -``` +``` \ No newline at end of file diff --git a/protected/humhub/docs/guide/admin/spaces.md b/protected/humhub/docs/guide/admin/spaces.md index 7ea45ecc08..b5e1c4c2ed 100644 --- a/protected/humhub/docs/guide/admin/spaces.md +++ b/protected/humhub/docs/guide/admin/spaces.md @@ -1,24 +1,24 @@ Spaces ======= -Spaces are one of the main concepts of HumHub for seperating and categorizing content like posts, wikis and polls. A space +Spaces are one of the main concepts of HumHub for separating and categorizing content like posts, wikis and polls. A space can be described as an independent content container with its own users, permissions and access settings. A space can be used by space members to share content with other users. ## Add Spaces Administrators can configure which groups are allowed to create new spaces under **Administration->Groups->Permissions**. -Please read the [Group Section](admin-groups.md) for more information about goups. +Please read the [Group Section](admin-groups.md) for more information about groups. Spaces can be added by clicking _Add Space_ within the _Space Navigation_. -A new space will require at least a space name. Futhermore you are able to define a space color, a description and advanced access settings. +A new space will require at least a space name. Furthermore, you are able to define a space color, a description and advanced access settings. The access settings consists of: - **Join Policy**: Describes who can join the new space. - - **Only by invite**: Only invited users can join the space. - - **Invite and request**: Users can request a space membership beside beeing invited. + - **Only by invitation**: Only invited users can join the space. + - **Invite and request**: Users can request a space membership besides beeing invited. - **Everyone can enter**: All members can join a space - **Visibility**: Who can view the space content. - - **Public**: All Users (Registered and Guests users). Available when allow limited access for non-authenticated users (guests) by admin settings. + - **Public**: All Users (Registered and Guests users). Available when allowing limited access for non-authenticated users (guests) by admin settings. - **Public (registered only)**: All registered users in Humbub (no guests) - **Private**: This space is invisible for non-space-members @@ -28,7 +28,7 @@ Users can be invited by clicking on the _Invite_ button within the space top men ## Approve New User -New user requests can be viewed, declinded or approved under _Space Settings -> Members -> Pending Approvals_ +New user requests can be viewed, declined or approved under _Space Settings -> Members -> Pending Approvals_ ## Pending Invites @@ -37,13 +37,13 @@ Pending Invites can be viewed and rejected under _Space Settings -> Members -> P ## Manage Space Members All members of a Space can be viewed under _Space Settings -> Members_. In this view space administrators are able to remove members -and assign a specfic space group to members. +and assign a specific space group to members. ## Manage Space Permission Space Permissions can be configured under _Space Settings -> Members -> Permissions. The permissions can be assigned to specific space groups which can either be assigned -to an user in the _Members_ configuration or are assigned by default (guests/normal user) +to a user in the _Members_ configuration or are assigned by default (guests/normal user) The available space groups are: - Owner: The owner of this space (the owner can be assigned by the founder of the group) diff --git a/protected/humhub/docs/guide/admin/troubleshooting.md b/protected/humhub/docs/guide/admin/troubleshooting.md index 8445d2bc17..54637c60db 100644 --- a/protected/humhub/docs/guide/admin/troubleshooting.md +++ b/protected/humhub/docs/guide/admin/troubleshooting.md @@ -19,7 +19,7 @@ Github - Bugtracker **When filing a new bug, please include:** - Descriptive title - use keywords so others can find your bug (avoiding duplicates) -- Steps to trigger the problem that are specific, and repeatable +- Specific and repeatable steps that trigger the problem - What happens when you follow the steps, and what you expected to happen instead. - Include the exact text of any error messages if applicable (or upload screenshots). - HumHub version (or if you're pulling directly from Git, your current commit SHA - use git rev-parse HEAD) @@ -28,6 +28,16 @@ Github - Bugtracker - Modules? Confirm that you've tested with Debug > Reload Without Extensions first (see below). - Any errors logged in Debug > Show Developer Tools - Console view +Cron Job Setup +---------------------------------------- +- Do you have access to setup Cron Jobs? +- Does your server use Cron or Crontab? +- Does your server use a third-party Cron Job provider? +- Are you using VPS or Dedicated/Shared/Other Hosting? +- Can you provide screenshots of your Cron Job settings? (With personal information blurred out!) +- What type of server are you using? (CloudLinux CentOS 6, Windows IIS, or etc) + +> Note: For more help refer to [here](cron-jobs.md)! Direct Support (Enterprise Edition only) ---------------------------------------- diff --git a/protected/humhub/docs/guide/admin/updating-020.md b/protected/humhub/docs/guide/admin/updating-020.md index 87824b774b..1cc75f7e3f 100644 --- a/protected/humhub/docs/guide/admin/updating-020.md +++ b/protected/humhub/docs/guide/admin/updating-020.md @@ -1,9 +1,8 @@ Updating to 0.20 ================ - > NOTE: This guide only affects updates from HumHub 0.11.2 or lower to HumHub 0.20 -1. Before you run an update please check, if your installed modules and themes are compatible with your targeted version. If not, you can follow the [Theme Migration Guide](theming-migrate.md) and [Module Migration Guide](dev-migrate.md) to make everything ready for the new version. +1. Before you run an update please check, if your installed modules and themes are compatible with your targeted version. If not, you can follow the [Theme Migration Guide](../theme/migrate.md) and [Module Migration Guide](../developer/migration-guide.md) to make everything ready for the new version. 2. Backup your data: - Backup the whole HumHub installation folder from your webroot @@ -12,7 +11,7 @@ Updating to 0.20 ## Migration Steps 1. Delete your current HumHub installation (Don't forget to make a backup as mentioned above, you will need these files later!) -2. Download the latest HumHub package directly from [http://www.humhub.org/downloads](http://www.humhub.org/downloads) and extract it to your webroot or install it via [GitHub/Composer](admin-installation.md). +2. Download the latest HumHub package directly from [https://www.humhub.org/download](https://www.humhub.org/download) and extract it to your webroot or install it via [GitHub/Composer](../developer/git-installation.md). 3. **IMPORTANT**: Before starting the Web installer you have to restore the /uploads/ directory form your backup to your new installation 4. Start the Web installer (e.g. [http://localhost/humhub](http://localhost/humhub)) and follow the instructions. If you enter the database name from your previous installation, HumHub will automatically migrate your existing database to the new version 5. Reinstall all previously installed modules/themes diff --git a/protected/humhub/docs/guide/admin/updating-automatic.md b/protected/humhub/docs/guide/admin/updating-automatic.md index 2548df2df6..2f7599d780 100644 --- a/protected/humhub/docs/guide/admin/updating-automatic.md +++ b/protected/humhub/docs/guide/admin/updating-automatic.md @@ -3,7 +3,7 @@ Automatic Updating > Warning: Please check before you run an update, that your installed modules and themes are compatible with the new version. If not, you can follow the migration guides. - [Theme Migration Guide](../theme/migrate.md) -- [Module Migration Guide](../developer/modules-migrate.md) +- [Module Migration Guide](../developer/migration-guide.md) > NOTE: Backup your data before updating! See: [Backup Chapter](backup.md) @@ -24,4 +24,4 @@ Updater Module Installation Troubleshooting --------------- -TBD \ No newline at end of file +TBD diff --git a/protected/humhub/docs/guide/admin/updating.md b/protected/humhub/docs/guide/admin/updating.md index f9f923ba99..04190e6b8e 100644 --- a/protected/humhub/docs/guide/admin/updating.md +++ b/protected/humhub/docs/guide/admin/updating.md @@ -1,18 +1,16 @@ Updating ======== - > Warning: Please check before you run an update, that your installed modules and themes are compatible with the new version. If not, you can follow the migration guides. - [Theme Migration Guide](../theme/migrate.md) -- [Module Migration Guide](../developer/modules-migrate.md) - +- [Module Migration Guide](../developer/migration-guide.md) > NOTE: Only use this guide if you want to update from HumHub 0.20 or higher! -> If you want to update from an older version (e.g. 0.11.2 or lower) to HumHub 0.20+, you have to use this guide: **[Upgrade to 0.20 and above](admin-updating-020.md "Guide: Upgrade to 0.20 and above")** +> If you want to update from an older version (e.g. 0.11.2 or lower) to HumHub 0.20+, you have to use this guide: **[Upgrade to 0.20 and above](updating-020.md "Guide: Upgrade to 0.20 and above")** > NOTE: Backup your data before updating! See: [Backup Chapter](backup.md) 1. Delete your current HumHub installation (Don't forget to make a backup as mentioned above, you will need these files later!) -2. Download the latest HumHub package from [http://www.humhub.org/downloads](http://www.humhub.org/downloads) and extract the package to your webroot +2. Download the latest HumHub package from [https://www.humhub.org/download](https://www.humhub.org/download) and extract the package to your webroot 3. Restore the following files from backup: - /uploads/* - /protected/runtime diff --git a/protected/humhub/docs/guide/basics/README.md b/protected/humhub/docs/guide/basics/README.md new file mode 100644 index 0000000000..e008645e01 --- /dev/null +++ b/protected/humhub/docs/guide/basics/README.md @@ -0,0 +1,11 @@ +# Basics + +Core Features +--------------- + +* [Markdown Cheatsheet](core/md-cheatsheet.md) +* [Emoji Cheatsheet](core/emoji-cheatsheet.md) + +Module Features +--------------- +`TBA` diff --git a/protected/humhub/docs/guide/basics/core/emoji-cheatsheet.md b/protected/humhub/docs/guide/basics/core/emoji-cheatsheet.md new file mode 100644 index 0000000000..bc7589bb38 --- /dev/null +++ b/protected/humhub/docs/guide/basics/core/emoji-cheatsheet.md @@ -0,0 +1,11 @@ +# Emoji Cheatsheet + +The following is a cheatsheet for the Emoji feature of HumHub + +| Emoji Code | Emoji Rendered| +| --- | --- | +| `:smile:` | :smile: | +| `:frown:` | :frown: (Doesn't render on GitHub because it is `frowning`) | +| `:tongue:`| :tongue: | + +> Will add more later as it will take some time to finish... diff --git a/protected/humhub/docs/guide/basics/core/md-cheatsheet.md b/protected/humhub/docs/guide/basics/core/md-cheatsheet.md new file mode 100644 index 0000000000..182be74469 --- /dev/null +++ b/protected/humhub/docs/guide/basics/core/md-cheatsheet.md @@ -0,0 +1,113 @@ +# Markdown Cheatsheet + +The following are supported Markdown syntax for the Social Networking Kit [HumHub](https://www.humhub.org) + +### Headers +``` +# This is an

tag +## This is an

tag +### This is an

tag +#### This is an

tag +##### This is an

tag +###### This is an
tag +``` + +### Blockquotes +``` +As Kanye West said: + +> We're living the future so +> the present is our past. +``` + +### Emphasis + +#### Bold +``` +**This text will be bold** +__This will also be bold__ +``` + +#### Italic +``` +*This text will be italic* +_This will also be italic_ +``` + +> Note: `_You **can** combine both as well_` + +### Code + +#### Inline Code +``` +I think you should use an +`` element here instead. +``` + +#### Syntax highlighting +``` +\```javascript +function fancyAlert(arg) { + if(arg) { + $.facebox({div:'#foo'}) + } +} +```\ +``` + +> Note: That all "`\`" must be removed and all "```" must be on its own line for Syntax highlighting to work! + +### Lists + +#### Unordered +``` +* Item 1 +* Item 2 + * Item 2a + * Item 2b +``` +#### Ordered +``` +1. Item 1 +2. Item 2 +3. Item 3 + a. Item 3a + b. Item 3b +``` + +### Links +``` +http://github.com - automatic! +[GitHub](http://github.com) +``` + +### Images + +#### Non-Linked Images +``` +![GitHub Logo](/images/logo.png) +Format: ![Alt Text](image url) - Note: Must have a image file type or won't work. +``` + +#### Linked Images +``` +[![Alt Text](/images/logo.png)](www.example.com) - Note: Must have a image file type or won't work. +``` + +### Tables +``` +First Header | Second Header +------------ | ------------- +Content from cell 1 | Content from cell 2 +Content in the first column | Content in the second column +``` + +### Strikethrough +``` +~~this~~ +``` + +### Ignoring Markdown formatting +``` +Let's rename \*our-new-project\* to \*our-old-project\*. +``` diff --git a/protected/humhub/docs/guide/bundled-software.md b/protected/humhub/docs/guide/bundled-software.md index d8d7a8fe85..fd96962c68 100644 --- a/protected/humhub/docs/guide/bundled-software.md +++ b/protected/humhub/docs/guide/bundled-software.md @@ -1,12 +1,12 @@ Bundled Software / Libaries =========================== -> This list may incomplete! +> This list may be incomplete! -* Yii Framework (Core Framework) http://www.yiiframework.com/ (protected/vendor/yiisoft/yii2/LICENSE.md) -* Zend Framework http://framework.zend.com/ (protected/vendor/zendframework/zend-\*/LICENSE.md) +* Yii Framework (Core Framework) http://www.yiiframework.com/ (protected/vendors/yii/LICENSE) +* Zend Framework http://framework.zend.com/ (protected/vendors/Zend/LICENSE.txt, protected/vendors/Zend2/LICENSE.txt) * Code Igniter (Security Functions) http://ellislab.com/codeigniter -* Various Yii Extensions +* Yii Extensions (See /protected/extensions/) * Yii-Mail - http://www.yiiframework.com/extension/mail/ * SwiftMailer - http://swiftmailer.org * jQuery - http://jquery.com diff --git a/protected/humhub/docs/guide/developer/README.md b/protected/humhub/docs/guide/developer/README.md index fd49d24a14..b62a0e85ea 100644 --- a/protected/humhub/docs/guide/developer/README.md +++ b/protected/humhub/docs/guide/developer/README.md @@ -4,41 +4,54 @@ Developement Guide Getting Started --------------- * [Overview](overview.md) -* [Git Installation](git-installation.md) -* [Development Environment](environment.md) +* [Git/Composer Installation](git-installation.md) * [Coding Standards](coding-standards.md) +* [Development Environment](environment.md) +* [Migration Guide](migration-guide.md) -Module Development + +Modules +--------------- +* [Getting Started](modules.md) +* [Asset Management](assets.md) +* [Change core behavior](module-change-behavior.md) +* [Embedded Themes](embedded-themes.md) +* [Migration / Updates](migration.md) + +Basic Concepts ------------------ -* [Introduction](modules-index.md) -* [Basic Structure](modules-structure.md) -* [Migration/Updates](modules-migrate.md) * [Content](content.md) -* [Events](modules-events.md) -* [Settings and Configuration](modules-settings.md) -* [Models / Database](modules-db.md) -* [Internationalization](modules-i18n.md) +* [Streams](stream.md) +* [Events](events.md) +* [Settings and Configuration](settings.md) +* [Models / Database](models.md) +* [Permissions](permissions.md) +* [Notifications](notifications.md) +* [Activities](activities.md) +* [File Handling](files.md) +* [Widgets](widgets.md) +* [Snippets](snippet.md) +* [Internationalization](i18n.md) Javascript API ------------------ * [Modules](javascript-index.md) * [Components](javascript-components.md) - * [Additions](javascript-uiadditions.md) + * [Additions](javascript-components.md) * [Actions](javascript-actions.md) * [Client](javascript-client.md) - * [Modals](javascript-modals.md) * [Events](javascript-events.md) + * [Widgets](javascript-widgets.md) + * [Pjax](javascript-pjax.md) - - -Special Topics +Advanced Topics -------------- -* [Notifications](notifications.md) -* [Activities](activities.md) +* [HumHub Build](build.md) * [Authentication](authentication.md) +* [Events](events.md) +* [Live Updates](live.md) * [Search](search.md) +* [Security](security.md) * [Console Application](console.md) -* [Streams](stream.md) -* [Permissions](permissions.md) -* [Widgets](widgets.md) + diff --git a/protected/humhub/docs/guide/developer/assets.md b/protected/humhub/docs/guide/developer/assets.md new file mode 100644 index 0000000000..521e0cf910 --- /dev/null +++ b/protected/humhub/docs/guide/developer/assets.md @@ -0,0 +1,5 @@ +Asset Management +===================== +(TBD) + +(TBD:getPublishedUrl) \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/build.md b/protected/humhub/docs/guide/developer/build.md new file mode 100644 index 0000000000..8efb0cb56b --- /dev/null +++ b/protected/humhub/docs/guide/developer/build.md @@ -0,0 +1,63 @@ +HumHub Build +============ + +## Setup + + 1. Install NPM + 2. Install Grunt (http://gruntjs.com/getting-started) + 3. call `npm update` in humhub root + +> Note: Since symlinks are not supported in some virtual machine shared folders the update command should be called from the host. + +## Setup grunt dependencies + +Call the following commands in your humhub root directory: + - `npm update` + - `npm install grunt --save-dev` + +## Build Assets + +HumHub uses Yiis build in mechanism for compressing and combining assets as javascript or stylesheet files in combination with grunt. + +Your compressed files will be saved under `/humhub/js/all-*.js` respectively `static/css/all-*.css`. + +> Note: HumHub will only use the compressed assets if operated in [production mode](admin-installation.md#disable-errors-debugging), otherwise +all assets will be served seperatly. + +### Grunt Asset Built + +The simples way to build your production assets is using the following grunt task: + +``` +grunt build-assets +``` + +### Manual Asset Built + +1. Delete the content of your `static/assets` directory. +2. Delete the old compressed file `static/js/all-*.js` and `static/css/all-*.css` +2. Run: + +``` +php yii asset humhub/config/assets.php humhub/config/assets-prod.php +``` + +> Info: More information is available in the [Yii Asset Guide](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html#combining-compressing-assets). + +## Build Community Theme + +To rebuild the community themes `theme.css` file you can execute one of the following commands: + +``` +lessc -x themes/HumHub/less/build.less themes/HumHub/css/theme.css +``` + +or with grunt: + +``` +grunt build-theme +``` + +### Other Grunt Tasks + - `grunt build-search` Rebuild your [Search Index](../admin/search.md) + diff --git a/protected/humhub/docs/guide/developer/content.md b/protected/humhub/docs/guide/developer/content.md index bb1211e398..35a7fb9525 100644 --- a/protected/humhub/docs/guide/developer/content.md +++ b/protected/humhub/docs/guide/developer/content.md @@ -1,6 +1,196 @@ Content ======= +Content record classes as for example `Post`, `Poll` and `Wiki` are subclasses of +[[\humhub\modules\content\components\ContentContainerActiveRecord]]. + Instances ofand are related to a +[[humhub\modules\content\models\Content]] record. +A ContentContainerActiveRecord subclass provides all features of a basic +Yii [ActiveRecords](http://www.yiiframework.com/doc-2.0/yii-db-activerecord.html) as validation and data access methods, + please refer to the [Yii Guide](http://www.yiiframework.com/doc-2.0/guide-db-active-record.html) for more information + about [ActiveRecords](http://www.yiiframework.com/doc-2.0/yii-db-activerecord.html). + +While the ContentContainerActiveRecord class contains the actual content data as texts and content settings, the related Content instance is beside others used to check **Permissions**, the **ContentContainer** relation, content **visibility** and is furthermore connected to ContentAddons as Like and Comments. + +Beside the basic ActiveRecord methods your ContentContainerActiveRecord class should at least implement the following functions + +- `getContentName()` - short name/type of content +- `getContentDescription()` - returns a short description of the content instance used to preview the content for example in activities etc. + +```php +class Example extends \humhub\modules\content\components\ContentContainerActiveRecord +{ + public static function tableName() + { + return 'example_content'; + } + + public function getContentName() + { + return Yii::t('ExampleModule.models_Example', "Example"); + } + + public function getContentDescription() + { + return $this->question; + } + + public function rules() + { + //return validation rules + } + + ..... + +} +``` + +Your content model should be **instantiated** as follows: + +```php +// Instantiate my model assign a content container and visibility. +$model = new MyModel(); +$model->content->container = $someSpace; +$model->content->container = Content::VISIBILITY_PRIVATE; +... +// Save model and content +$model->save(); +``` + +Get the model instance from a given content instance: + +```php +$model = $content->getPolymorphicRelation(); +``` + +Calling [[\humhub\modules\content\components\ContentActiveRecord::find()]] will return a [[\humhub\modules\content\components\ActiveQueryContent]] with additional methods to select specific content: + +```php +// Returns all MyModels related to the given $space +$models = MyModel::find()->contentContainer($space)->all(); + +// Returns all MyModels related to the given $space and readable by the current user +$models = MyModel::find()->contentContainer($space)->readable()->all(); + +// Loads all MyModels of the current users member spaces +$models = MyModel::find()->userRelated([ActiveQueryContent::USER_RELATED_SCOPE_SPACES])->all(); + +// Loads all readable MyModels of the current users spaces and also followed spaces +$models = MyModel::find()->userRelated([ + ActiveQueryContent::USER_RELATED_SCOPE_SPACES, + ActiveQueryContent::USER_RELATED_SCOPE_FOLLOWED_SPACES +])->readable()->all(); +``` + +There are the following user related scopes available: + +- `USER_RELATED_SCOPE_OWN` Content created by the given user itself (`content.created_by`) +- `USER_RELATED_SCOPE_SPACES` Content related to the users member spaces +- `USER_RELATED_SCOPE_FOLLOWED_SPACES` = Content related to the users followed spaces +- `USER_RELATED_SCOPE_FOLLOWED_USERS` = Content related to the users followed user profiles +- `USER_RELATED_SCOPE_OWN_PROFILE` = Content related to the users own profile + +### Content features + +**Content visibility** + +The content visibility can be checked by calling `isPublic()` and `isPrivate()`. + +```php +$model->content->isPublic(); + +$model->content->isPrivate(); + +// Set visibility +$model->content->container = Content::VISIBILITY_PRIVATE; +``` + +**Pin content** + +The default space stream supports the pinning of content, which will load the pinned entries at the top of the +stream. Normally you won't need to call the pin/unpin methods by yourself, since this is part of the default stream +entry logic. In case your content is not part of the default stream, you may use these functions for your own module logic. + +```php +$model->content->pin(); + +$model->content->unpin(); + +$model->content->isPinned(); + +$model->content->canPin(); +``` + +**Archive content** + +Archived content is by default excluded from the streams. As with the pin logic, you won't have to handle this by yourself. + +```php +$model->content->archive(); + +$model->content->unarchive(); + +$model->content->isArchived(); + +$model->content->canArchive(); + +``` + +**Content Url** + +By default the `Content::getUrl()` returns the permalink of the wallentry. In case the content is used outside of the default stream, this behaviour can be changed by implementing a `getUrl()` method in your ContentActiveRecord class. + +```php +$permaLink = $model->content->getUrl(); +``` + +### Check content permissions + +By default a user can edit a content if one of the following conditions defined in `Content::canEdit()` are met: + +- User is the owner of the content +- User is system administrator and the content module setting `adminCanEditAllContent` is set to true (default) +- The user is granted the space ManagePermission set by the model record class. Since v1.2.1 a ContentContainerActiveRecord can define an own `$managePermission` which will be described later. +- The user meets the additional condition, which is defined in the ContentContainerActiveRecords `canEdit()` function. + +You can check the edit permission of a user by means of the `Content::canEdit()` function as + +```php +// Check edit permission for current logged user +if($model->content->canEdit()) {...} + +// Check edit permission for a given user +if($model->content->canEdit($someUserIdentity)) {...} + +// Check other permission for the current logged user on the contents contentContainer +if($model->content->can(new MyCustomPermission()) {...} +``` + +Since HumHub v1.2.1 you can overwrite the default ManageContent permission as follows: + +```php +class Example extends ContentContainerActiveRecord +{ + $managePermission = MyCustomManagePermission::class; + ..... +} +``` + +The default `Content::canView()` permission behaviour of content is handled as follows + +- Guests can only access public content of visible spaces/users +- Other users can access all public content within the network +- System admins can access all content if the `adminCanViewAllContent` setting of the `content` modules is enabled (default) +- All space members can access private space content +- Non space members can only access public space content +- Only friend users can access prviate profile content of a user. + +```php +if($model->content->canView()) {...} +``` + +>Info: For more information about permissions, please see the [Permission Section](module-permissions.md). + ## ContentContainer A [[humhub\modules\content\models\ContentContainer|ContentContainer]] in HumHub is the base concept for assigning content entries to a specific container instance (user or space). @@ -27,8 +217,6 @@ The [[humhub\modules\content\components\ContentContainerController|ContentContai - Layout selection based on container type (User or Space) - Create URL's for the given ContentContainer -For example: - ```php class ExampleController extends \humhub\modules\content\components\ContentContainerController { @@ -41,8 +229,7 @@ class ExampleController extends \humhub\modules\content\components\ContentContai } ``` -Url's pointing to a ContentContainer action should be created by using the `createUrl()` function -of your ContentContainer instance. This will add the required sguid or uguid to your request. +Urls pointing to a ContentContainer action should be created by using the `createUrl()` function of your ContentContainer instance. This will add the required sguid or uguid to your request. ```php // Direct ContentContainer call @@ -75,112 +262,19 @@ if($profileImage->hasImage()) { ### ContentContainerModule -If a module should appear in the content containers module section, the module class must extend [[humhub\modules\content\components\ContentContainerModule]]. -A ContentContainerModule can be enabled or disabled for a specific ContentContainer. The calendar module, for example, can be enabled for a specific space or a specific user account. +See the [Getting Started](modules-index.md) section -See the [[humhub\modules\content\components\ContentContainerModule]] class for a full list of options. +#### Content Streams -Example of a modules `Module.php` file: +See the [Stream](stream.md) section -```php -class Module extends \humhub\modules\content\components\ContentContainerModule -{ - - // Defines for which content container type this module is appropriate - public function getContentContainerTypes() - { - // This content container can be assigned to Spaces and User - return [ - Space::className(), - User::className(), - ]; - } - - // Is called when the whole module is disabled - public function disable() - { - // Clear all Module data and call parent disable - parent::disable(); - } - - // Is called when the module is disabled on a specific container - public function disableContentContainer(ContentContainerActiveRecord $container) - { - parent::disableContentContainer($container); - //Here you can clear all data related to the given container - } - - // Can be used to define a specific description text for different container types - public function getContentContainerDescription(ContentContainerActiveRecord $container) - { - if ($container instanceof Space) { - return Yii::t('MyModule.base', 'Description related to spaces.'); - } elseif ($container instanceof User) { - return Yii::t('MyModule.base', 'Description related to user.'); - } - } -``` -> Note: If you're working with content or other persistent data, make sure to delete container related data when the module is disabled on a contentcontainer. This can be archieved by overwriting the [[humhub\modules\content\components\ContentContainerModule::disableContentContainer]] function. - - -## Content - -TBD - -### ContentActiveRecord - -Each Content ActiveRecord (derived from [[\humhub\modules\content\components\ContentActiveRecord]]) is automatically linked to a [[humhub\modules\content\models\Content]] record via the *content* attribute. - -This Content record holds all neccessary information and provides common methods: - -- ContentContainer which the Content belongs to -- Meta Information (created_at, created_by, updated_at, updated_by) -- Wall Assignments / Methods -- Archiving / Pinning -- And more... - -If you're implementing an ActiveRecord based on [[humhub\modules\content\components\ContentContainerActiveRecord]] you need to implement the following abstract methods: - -- `getContentName()` - Returns the displayed name of the Content (e.g. Post or Poll) -- `getContentDescription()` - Returns a preview of the Content - which is used in Notifications for example. - -Example: - -```php - (TBD) - -``` - -#### Wall/Stream Output +## Global content (TBD) -#### Querying Content - -If you're calling find() on a [[\humhub\modules\content\components\ContentActiveRecord]] instance you'll get a special [[\humhub\modules\content\components\ActiveQueryContent]] which provides additional methods to select content. - -- contentContainer($container) - Find content only inside a given container -- readable($user) - Return only user readable content -- ... - - -### Controller - -TBD - -## ContentAddon +## Content addons TBD - Always linked to particual Content, inherits access rules from it - Examples: Like, File, Comment - Can be nested (e.g. Container -> Content -> Addon (Comment) -> Addon (Like) - -### ActiveRecord - -TBD - -Provides access to the related content via *content *Attribute - -### Controller - -TBD diff --git a/protected/humhub/docs/guide/developer/contributing.md b/protected/humhub/docs/guide/developer/contributing.md index d48c26844f..eb8dd0a05d 100644 --- a/protected/humhub/docs/guide/developer/contributing.md +++ b/protected/humhub/docs/guide/developer/contributing.md @@ -1,3 +1,24 @@ # Contributing -TBD +## Report an Issue + +**Do not report an issue if** + +- *you are asking how to use a HumHub feature. You should use [the community](https://community.humhub.com) for this purpose.* +- *your issue is about security. Please [contact us directly](mailto:info@humhub.com) to report security issues.* + +**Avoid duplicated issues** + +Before you report an issue, please search through [existing issues](https://github.com/buddh4/humhub-docs/issues) to see if your issue is already reported or fixed to make sure you are not reporting a duplicated issue. +Also make sure you have the latest version of HumHub and see if the issue still exists. + +## Contribute to the Docs or fix grammar errors (Pull Requests) + +Third-party patches are essential for keeping the HumHub Docs great. +We want to keep it as easy as possible to contribute changes that get things working in your environment. +There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. + +- *Make sure you have a [GitHub account](https://github.com/signup/free)* +- *Fork the repository on GitHub* +- *Make sure there isn't an issue created for the thing you are working on if it requires significant effort to fix* +- *Open a pull request* diff --git a/protected/humhub/docs/guide/developer/embedded-themes.md b/protected/humhub/docs/guide/developer/embedded-themes.md new file mode 100644 index 0000000000..7a979b72a3 --- /dev/null +++ b/protected/humhub/docs/guide/developer/embedded-themes.md @@ -0,0 +1,2 @@ +Embedded themes +================= \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/environment.md b/protected/humhub/docs/guide/developer/environment.md index e63e7fa3cb..ad3fd88cd5 100644 --- a/protected/humhub/docs/guide/developer/environment.md +++ b/protected/humhub/docs/guide/developer/environment.md @@ -4,50 +4,53 @@ Development Environment Quick Notes ----------- -- Make sure you are using a [Git/Composer HumHub Installation](git-installation.md) -- Enable development mode, see [Disable Errors Section](../admin/security.md#disable-errors-debugging) -- Disable Caching under `Administration -> Settings -> Advanced -> Caching -> None` +- Make sure you are using a [Git/Composer HumHub installation](git-installation.md) +- Enable the debug mode, see [Disable Errors Section](../admin/security.md#disable-errors-debugging) +- Disable caching under `Administration -> Settings -> Advanced -> Caching -> None` -> Tip: If you are working on a windows machine, but wan't to operate your test environment on linux, you should consider using a virtual machine with a shared host directory. - -Modules Directory +External Modules Directory ----------------- -You can locate your custom modules outside of the HumHub project directory by means of the following [configuration](../admin/advanced-configuration.md#application-params) +Custom modules can also be located outside of the default HumHub modules directory by +setting the `moduleAutoloadPaths` parameter in your `/protected/config/common.php` configuration. This seperation can +be useful while working with custom modules. ```php return [ + //... 'params' => [ 'moduleAutoloadPaths' => ['/some/folder/modules'], - ] + ], + //... ] ``` - - Yii Debug Module ---------------- -Add the following block to your local web configuration `/protected/config/web.php` to enable the [Debug Extension for Yii 2](http://www.yiiframework.com/doc-2.0/ext-debug-index.html) +Add following block to your local web configuration `/protected/config/web.php` in order +to allow [Yii's debug Module](http://www.yiiframework.com/doc-2.0/ext-debug-index.html). ```php - ['debug'], - 'modules' => [ - 'debug' => [ - 'class' => 'yii\debug\Module', - 'allowedIPs' => ['127.0.0.1', '::1'], - ], - ] + 'modules' => [ + // ... + 'debug' => [ + 'class' => 'yii\debug\Module', + 'allowedIPs' => ['127.0.0.1', '::1'], + ], + // ... + ] ]; -?> ``` Gii Code Generator ------------------- -Add following block to your local web configuration `/protected/config/web.php` to enable the [Gii Code Generator](http://www.yiiframework.com/doc-2.0/guide-start-gii.html) + Add following block to your local web configuration `/protected/config/web.php` in order to + enable the [Gii Code Generator Module](http://www.yiiframework.com/doc-2.0/guide-start-gii.html) ```php return [ @@ -64,7 +67,7 @@ return [ ?> ``` -Furthermore add the following block to your local console configuration (/protected/config/console.php) +Furthermore add the following block to your local console configuration `/protected/config/console.php` ```php return [ @@ -77,76 +80,7 @@ return [ ]; ``` -> Tip: Check out the [HumHub devtools](https://github.com/humhub/humhub-modules-devtools) module for a HumHub module generator and some showcases and tutorials. At the time of writing, the devtools Module is still under developement. - -HumHub Build ------------ - -Some of the tasks - -### Setup - - 1. Install NPM - 2. Install Grunt (http://gruntjs.com/getting-started) - 3. call `npm update` in humhub root - -> Note: Since symlinks are not supported in some virtual machine shared folders the update command should be called from the host. - -#### Setup grunt dependencies - -Call the following commands in your humhub root directory: - - `npm update` - - `npm install grunt --save-dev` - -### Build Production Assets - -HumHub uses Yiis build-in mechanism for compressing and combining assets as javascript or stylesheet files in combination with grunt. - -Your compressed files will be saved under `/humhub/js/all-*.js` respectively `static/css/all-*.css`. The task will also rebuild your `static/assets` folder, which contains dependent production assets. - -> Info: HumHub will only use the compressed assets if operated in [production mode](admin-installation.md#disable-errors-debugging), otherwise all assets will be served seperatly. - -##### Grunt Asset Built - -The simples way to build your production assets is using the following grunt task: - -``` -grunt build-assets -``` - -##### Manual Asset Built - -1. Delete the content of your `static/assets` directory. -2. Delete the old compressed file `static/js/all-*.js` and `static/css/all-*.css` -2. Run: - -``` -php yii asset humhub/config/assets.php humhub/config/assets-prod.php -``` - -> Info: More information is available in the [Yii Asset Guide](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html#combining-compressing-assets). - -### Build Theme - -To rebuild the community themes `theme.css` file you can execute one of the following commands: - -``` -lessc -x themes/HumHub/less/build.less themes/HumHub/css/theme.css -``` - -or with grunt: - -``` -grunt build-theme -``` - -You can also build other themes within your `themes` folder as follows - -``` -grunt build-theme --name=myTheme -``` - - -### Other Grunt Tasks - - `grunt build-search` Rebuild your [Search Index](../admin/search.md) +Developer Tools Module +------------------- +The [devtools Module](https://github.com/humhub/humhub-modules-devtools) provides useful showcases of widgets and also a Module generator based on Gii. diff --git a/protected/humhub/docs/guide/developer/events.md b/protected/humhub/docs/guide/developer/events.md new file mode 100644 index 0000000000..bd82c5fe33 --- /dev/null +++ b/protected/humhub/docs/guide/developer/events.md @@ -0,0 +1,41 @@ +Events +====== + +TBD + +[http://www.yiiframework.com/doc-2.0/guide-concept-events.html](http://www.yiiframework.com/doc-2.0/guide-concept-events.html) + + + +### Catching an Event + +Example event section of the config.php file: + +```php +// ... +'events' => [ + [ + 'class' => \humhub\widgets\TopMenu::className(), + 'event' => \humhub\widgets\TopMenu::EVENT_INIT, + 'callback' => [Module::className(), 'onTopMenuInit'], + ], + // ... + ] +// ... +``` + +### Processing + +Example of event callback: + +```php +public static function onTopMenuInit($event) +{ + $event->sender->addItem(array( + 'label' => "Example", + 'icon' => '', + 'url' => '#', + 'sortOrder' => 200, + )); +} +``` diff --git a/protected/humhub/docs/guide/developer/git-installation.md b/protected/humhub/docs/guide/developer/git-installation.md index 1fc8abe7cc..6bd8553b5d 100644 --- a/protected/humhub/docs/guide/developer/git-installation.md +++ b/protected/humhub/docs/guide/developer/git-installation.md @@ -1,11 +1,9 @@ -Git Installation (Developers) +Installation (Developers) ========================= - -> Warning: This installation method allows you to fetch the latest version from our git repository. Since the master branch is not always in a stable state, this branch is not intended for production use. +> Warning: This installation method allows you to fetch the latest branch from the repository which may not stable enough for production use. Preparation ----------- - Create a MySQL Database, e.g.: ```sql @@ -16,53 +14,23 @@ FLUSH PRIVILEGES; > Note: Do not forget to change the `humhub_dbuser` and `password_changeme` placeholders! - Get HumHub ---------- - -In order to be able to install a branch fetched by git, you'll have to run a composer update to download external dependencies. - - 1. Clone Git Repository: + - Install [git](https://git-scm.com/) + - Clone git Repository: ``` git clone https://github.com/humhub/humhub.git ``` - - 2. Install composer ([https://getcomposer.org/doc/00-intro.md](https://getcomposer.org/doc/00-intro.md)) - 3. Navigate to your HumHub webroot and fetch dependencies: - + - Install composer ([https://getcomposer.org/doc/00-intro.md](https://getcomposer.org/doc/00-intro.md)) + - Navigate to your HumHub webroot and fetch dependencies: ``` -php composer.phar global require "fxp/composer-asset-plugin:^1.4.2" +php composer.phar global require "fxp/composer-asset-plugin:~1.3" php composer.phar update ``` -> Note: The composer update may have to be executed again after an update of your local repository by a git pull. Read more about updating ([Update Guide](admin-updating.html#gitcomposer-based-installations)) - - - -File Settings & Permissions ---------------------------- - - -Make the following directories/files writable by the webserver -- /assets -- /protected/config/ -- /protected/modules -- /protected/runtime -- /uploads/* - -Make the following files executable: - - /protected/yii - - /protected/yii.bat - -> Warning: Make sure the following directories are not accessible through the webserver! - - -Start Web Installer ---------------- - -Open the installation guide in your browser (e.g. [http://localhost/humhub](http://localhost/humhub)) - - +> Note: The composer update may have to be executed again after an update of your local repository by a git pull. Read more about updating ([Update Guide](../admin/updating.md)) + + - Follow further instructions of the [Installation Guide](../admin/installation.md) diff --git a/protected/humhub/docs/guide/developer/i18n.md b/protected/humhub/docs/guide/developer/i18n.md new file mode 100644 index 0000000000..f994f0fe4b --- /dev/null +++ b/protected/humhub/docs/guide/developer/i18n.md @@ -0,0 +1,28 @@ +Internationalization (I18N) +=========================== + +**Optionally** you can use following module translation method instead of Yii's standard approach (). + +For Internationalization to work properly, you should also consider the notes on [Setting Up PHP Environment](http://www.yiiframework.com/doc-2.0/guide-tutorial-i18n.html#setup-environment) +from the Yii Guide. + + +### Message Category + +Following message category syntax is automatically mapped against your modules *messages* folder. + +```php +Yii::t('ExampleModule.some_own_category', 'Translate me'); +``` + +Base Category Naming Examples: + +- polls -> PollsModule +- custom_pages -> CustomPagesModule + + +### (Re-) Generate message files + +Example message creation command for module with id *example*: + +> php yii message/extract-module *example* diff --git a/protected/humhub/docs/guide/developer/javascript-components.md b/protected/humhub/docs/guide/developer/javascript-components.md index 816dceb134..64ca149810 100644 --- a/protected/humhub/docs/guide/developer/javascript-components.md +++ b/protected/humhub/docs/guide/developer/javascript-components.md @@ -1,7 +1,7 @@ Javascript UI Components ======= -UI Components can be used to bind specific parts of your view to a Javascript Widgets defined in your module. This can be achieved by extending the `action.Component` or the more powerful `ui.Widget` class. +UI Components can be used to bind specific dom parts of your view to a Javascript Widgets defined in your module. This can be achieved by extending the `action.Component` or the more powerful `ui.Widget` class. ## Simple Components @@ -29,13 +29,13 @@ humhub.module('example.MyComponent', function(module, require, $) { Component.call(this, node, options); } - // Make sure this is called before your function definitions, otherwise they will be lost! object.inhertis(MyComponent, Component); MyComponent.prototype.hello = function(evt) { this.$.find('.message').text('Hi!'); } + // Export a single class module.export = MyComponent; }); ``` @@ -46,7 +46,7 @@ After clicking the button of the previous example the `action` module will searc If you need the instance of your component, for example in another module, you can retrieve it by calling `Component.instance`: -```javascript +```Javascript humhub.module('example.two', function(module, require, $) { var Component = require('action').Component; @@ -75,12 +75,12 @@ Components can be nested, which can be handy for example if you want to implemen ###### Module: -```javascript +```Javascript humhub.module('example.mylist', function(module, require, $) { var object = require('util').object; var Component = require('action').Component; - // our parent component + // our parent component var List = function(node, options) { Component.call(this, node, options); } @@ -115,22 +115,21 @@ humhub.module('example.mylist', function(module, require, $) { }); }); ``` -> Note: The `data` function of a component will search for a given data attribute on the components own root node and if not present will search the parent components for the data attribute. ## Widgets -The `humhub.modules.ui.widget.Widget` class extends the `action.Component` class and provides some additional functionality as: +The `humhub.modules.ui.widget.Widget` class extends the `Component` class and provides some additional functionality as: - Advanced event handling -- Eager initialization +- Eager or lazy initialization - Widget options #### Widget Initialization -A Widgets `init` function is called once the widget is created. A Widget is created either immediately within the humhub initialization phase in case the widgets root node contains a `data-ui-init` flag or lazily by calling a widget action or initializing the Widget by means of calling `Widget.instance('#myWidget')`. +A Widgets `init` function is called once the widget is created. A Widget is created either immediately within the humhub initialization phase in case the widgets root node contains a `data-ui-init` flag or by lazily creating it when calling a widget action or initializing the Widget by means of calling `Widget.instance('#myWidget')`. -> Note: If you load a Widget by an ajax call, make sure to apply the [ui.additions](javascript-uiadditions.md) on your inserted dom node, otherwise the `data-ui-init` behaviour won't be recognized. +> Note: If you load a Widget by an ajax call, make sure to apply the `ui.additions` on your inserted dom nodes, otherwise the `data-ui-init` behavriour won't be recognized. ###### View: ```php @@ -157,13 +156,14 @@ humhub.module('example.MyWidget', function(module, require, $) { this.$.fadeIn('fast'); } + // Export a single class module.export = MyWidget; }); ``` #### Widget Options -Your Widget options can be set by using `data-*` attributes on your Widgets root node. +Your widgets option can be set by using `data-*` attributes on your Widgets root node. The Widgets `getDefaultOptions()` method can be used to define default Widget options. ###### View: @@ -184,6 +184,7 @@ humhub.module('example.MyWidget', function(module, require, $) { Widget.call(this, node, options); } + // Make sure this is called before your function definitions, otherwise they will be lost! object.inhertis(MyWidget, Widget); var MyWidget.prototype.getDefaultOptions = function() { @@ -200,6 +201,7 @@ humhub.module('example.MyWidget', function(module, require, $) { } } + // Export a single class module.export = MyWidget; }); ``` @@ -211,9 +213,26 @@ TBD #### JsWidget class -In order to implement a Yii widget responsible for rendering your widgets markup, you can extend hte [[humhub\widgets\JSWidget]] class as in the following examples. +In order to implement a Yii widget responsible for rendering your widgets markup, you can implement a PHP class derivated of [[humhub\widgets\JSWidget]] as in the following examples. -##### Default widget rendering: +Here are some of the available attributes of the JSWidget class: + +- `id`: the widget root id, if not provided a generated id will be used by default +- `jsWidget`: defines the Javascript widget namespace +- `init`: will add the data-ui-init flag if set to true +- `visible`: can be set to false in case the root node should be rendered hidden on startup +- `options`: used to overwrite or set the Widgets htmlOptions +- `events`: defines widget action events +- `container`: defines the root node name when using the default rendering mechanism +- `content`: defines the content of the root node when using the default rendering mechanism + +Functions: + +- `getData()`: returns an array of widget settings which will be transformed into `data-*` attributes. +- `getAttributes()`: returns an array of html attributes/values +- `getOptions()`: merges the given `options` with the result of `getData()` and `getAttributes()` and is used as root node options in most of the cases + +###### Default widget rendering: The following example shows a simple JsWidget implementation without overwriting the widgets `run` method. @@ -248,24 +267,6 @@ class MyWidget extends \humhub\widgets\JsWidget } } ``` - -The following `JSWidget` attributes are available: - -- `id`: the widget root id, if not provided a generated id will be used by default -- `jsWidget`: defines the Javascript widget namespace -- `init`: will add the data-ui-init flag if set to true -- `visible`: can be set to false in case the root node should be rendered hidden on startup -- `options`: used to overwrite or set the Widgets htmlOptions -- `events`: defines widget action events -- `container`: defines the root node name when using the default rendering mechanism -- `content`: defines the content of the root node when using the default rendering mechanism - -Functions: - -- `getData()`: returns an array of widget settings which will be transformed into `data-*` attributes. -- `getAttributes()`: returns an array of html attributes/values -- `getOptions()`: merges the given `options` with the result of `getData()` and `getAttributes()` and is used as root node options in most of the cases - > Note: in this case the `container` setting could be omitted, since `div` is the default container name. The widget could be used within a view as follows: @@ -285,7 +286,7 @@ which would render the following output:
Some content
``` -##### Custom widget rendering: +###### Custom widget rendering: For more complex JsWidgets, you can overwrite your widgets `run` method and use the `getOptions` method to merge the widgets `options` with the default options provided by `getData` und `getAttributes` as follows. diff --git a/protected/humhub/docs/guide/developer/javascript-index.md b/protected/humhub/docs/guide/developer/javascript-index.md index cc67db512f..a4f43308ce 100644 --- a/protected/humhub/docs/guide/developer/javascript-index.md +++ b/protected/humhub/docs/guide/developer/javascript-index.md @@ -1,18 +1,19 @@ -Javascript Modules +Javascript API Getting Started ======= Since version 1.2, HumHub provides a module based Javascript API within the `humhub` namespace. -Instead of embeding inline script blocks into your views, it's highly recommended to store your Javascript code in external script files and ideally use the HumHub module system. - -Your module scripts should reside in the `resources/js` directory of your modules root directory. +Instead of embeding inline script blocks into your views, it's highly recommended to store your Javascript code in external script files and use the HumHub module system. ## Modules System ### Publish a Module Asset -In order to add your script files to your view, you should use an [Asset Bundle](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html) residing within the `assets` directory of your module. +Your script files should reside within the `resources/js` folder of your humhub module and should ideally be appended at the bottom of the document. + +In order to add a Javascript module file to your view, you should use a [Asset Bundle](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html) class residing within the `assets` directory of your module. By setting `public $jsOptions = ['position' => \yii\web\View::POS_END];`, your assets will be appended to the end of the document body. This will assure all core modules are already registered. + +Be careful though, when you want to edit your files in `resources/js`. All asset files are bundled and then copied to the `@webroot`-folder of your application, when registered in the view for the first time (see below). In order to force your module assets to be re-published with each requests, you can add the publish option `forceCopy` as in the following example. This can be useful while developing your module, but don't forget to disable this option in official releases! -By setting `public $jsOptions = ['position' => \yii\web\View::POS_END];`, your assets will be appended to the end of the document body. This will assure all core modules are already registered. If you require your script beeing loaded ealrier you can also set `\yii\web\View::POS_BEGIN`, which will add you script at the beginning of the document body. ```php namespace humhub\modules\example\assets; @@ -21,6 +22,9 @@ use yii\web\AssetBundle; class ExampleAsset extends AssetBundle { + public $publishOptions = [ + 'forceCopy' => true + ]; public $jsOptions = ['position' => \yii\web\View::POS_END]; public $sourcePath = '@example/resources'; public $js = [ @@ -37,24 +41,23 @@ The [Asset Bundle](http://www.yiiframework.com/doc-2.0/guide-structure-assets.ht Where `$this` is the view instance. More infos about Asset Bundles are available in the [Yii Guide](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html). + +If your bundle is registered to a view retrieved by an ajax call, make sure to render your view by using your controllers `$this->renderAjaxContent()` function. In contrast to `renderPartial()`, this function will add all your asset dependencies to your partial content. + > Note: Make sure to add your assets after the core scripts, which are added within the documents head section. -> Note: Yii loads Javascript Files only once per page load, therefore all your script files will only be loaded and executed once. This can lead to unexpected behaviour especially with [Pjax](javascript-client.md#pjax) single page loading enabled. - -> Note: If your bundle is registered to a view retrieved by an ajax call, make sure to render your view by using your controllers `$this->renderAjaxContent()` function. In contrast to `renderPartial()`, this function will add all your asset dependencies to your partial content. Don't use the `renderAjaxContent` to include a view into your page outside of an ajax call, this will include some script twice and will lead to unexpected behaviour. +> Note: Yii loads Javascript Files only once per page load, therefore all your script files will only be loaded and executed once. This can lead to unexpected behaviour especially with [Pjax](javascript-client.md) single page loading enabled. ### Register Modules -Modules are registered by calling the `humhub.module()` function as follows +Modules are registered by calling the `humhub.module` function as follows ```javascript humhub.module('example', function(module, require, $) { ... }); ``` -The previous `example` module will be added to the following namespace `humhub.modules.example`. - -You can also register sub modules as in the following example +**Submodules** can be registered as follows ```javascript humhub.module('example.mySubModule', function(module, require, $) { @@ -62,9 +65,9 @@ humhub.module('example.mySubModule', function(module, require, $) { }); ``` -The first argument of the `humhub.module()` function defines the **module id** which also defines the namespace appended to the `humhub.modules`. The second argument is the actual **module function**. +The first argument of the `humhub.module` function defines the **module id** which will be appended to the `humhub` namespace. The previous `example` module will be added to the following namespace `humhub.modules.example`. The second argument is the actual **module function**. -> Note: You should use an unique namespace for your custom modules as `myproject.mymodule`, otherwise it may interfere with other modules. +> Note: Your module id has to be unique amongst all available modules and should ideally be consistent with your backend module id. Your module function is provided with the following arguments: @@ -72,18 +75,15 @@ Your module function is provided with the following arguments: 2. `require` - Used for injecting other modules. 3. `$` - jQuery instance. -#### Export Module Logic +##### Export Module Logic Module functions and attributes can only be accessed outside of the module if they are exported, either by directly appending them to the `module` instance or by calling `module.export`. ```javascript humhub.module('example', function(module, require, $) { - - // private function - var private = function() { /* ... */ } - // direct export of public function - module.publicFunction = function() {/* ... */} + // export public function + module.myPublicFunction = function() {/* ... */} // another public function exported later var publicTwo = function() { /* ... */} @@ -95,27 +95,28 @@ humhub.module('example', function(module, require, $) { }); }); ``` + In case you only want to **export a single object/function/class** you can use the following syntax: ```javascript -/* ... */ +[...] var MyClass = function() {/*...*/}; MyClass.prototype.myFunction = function() {/*..*/} module.export = MyClass; -/* ... */ +[...] ``` -> Note: When exporting a single object or class, the plain object or function will be added to the given namespace without including the usual module attributes and utilities mentioned later in this guide. +> Note: When exporting a single object or class, the exported object won't have the usual module attributes and utilities. The plain object or function will simply be added to the namespace. -#### Module Initialization +##### Module Initialization -Your module can define its initialization logic by exporting an `init` function. +Your module can define its initialization logic by implementing and exporting an `init` function. -By default this function is only called once after a full page load or directly after the registration in case the module was loaded per ajax. If your module requires an initialization also after [Pjax](javascript-client.md) page loads, your module has to set the `initOnPjaxLoad` flag. In this case the `init` function will provide an `isPjax` flag for beeing able to distinguish between full page loads and [Pjax](javascript-client.md) page loads. +By default this function is only called once after a full page load or directly after the registration in case the module was loaded per ajax. If your module requires an initialization also after [Pjax](javascript-client.md) page loads, your module has to set the `initOnPjaxLoad` flag. In this case the `init` function will provide an `isPjax` parameter for beeing able to distinguish between full page loads and [Pjax](javascript-client.md) page loads. ```javascript -/* ... */ +[...] module.initOnPjaxLoad = true; var init = function(isPjax) { @@ -130,15 +131,14 @@ var init = function(isPjax) { module.export({ init: init }); -/* ... */ +[...] ``` -> Tip: You'll need the `initOnPjaxLoad` flag for modules which rely on specific dom elements or specific views. -> Warning: Once registered, your modules `init` function may be called even if you are not currently in your desired modules view. This occures especially if [Pjax](javascript-pjax.md) is enabled and `initOnPjaxLoad` is set to `true`. Therfore, if your modules initialization logic only makes sense in a specific context, make sure you reside in the desired view before running your actual initialization code e.g: `if(!$('#mySpecialViewElement').length) {return;}`. +> Warning: Once registered, your modules `init` function may be called even if you are not currently in your desired modules view. This occures especially if [Pjax](javascript-pjax.md) is enabled and `initOnPjaxLoad` is set to `true`. Therfore, if your modules initialization logic only makes sense in a specific context, make sure you reside in the desired view before running your actual initialization code e.g: `if(!$('#mySpecialElement').length) {return;}`. -#### Module Unload +##### Module Unload -For the purpose of cleaning up module related dom nodes etc. your module can export an `unload` function which is called before each Pjax page load. This function is mainly used to remove obsolete dom nodes outside of the main content area, prevent memory leaks, remove obsolete dom listeners or clear up some module data. +For the purpose of cleaning up module related dom nodes etc. there is also an `unload` function which is called before each Pjax page load. This function is mainly used to remove obsolete dom nodes, prevent memory leaks, remove obsolete dom listeners or clear some module data. ```javascript var unload = function($pjax) { @@ -152,7 +152,7 @@ module.export({ > Note: Some third party libraries append helper elements to the document body. Make sure to remove such elements in the `unload` function. -#### Module Dependencies +##### Module Dependencies Other modules can be injected into your module by using the `require` function as follows @@ -169,7 +169,7 @@ require('example').myFunction(); humhub.modules.example.myFunction(); ``` -It's good practice to require all dependent modules at the beginning of your module. When doing so consider the loading order of those modules. Since all core modules are registered in the head section of your document, they are available within the document body. +It is a good practice to require all dependent modules at the beginning of your module. You should only require modules at the beginning of your module, if you are sure the required module is already registered. Since all core modules are registered in the head section of your document, they are available within the document body. If your module requires another module, which is not part of the core API, you can ensure the order by means of the `$depends` attribute of your Asset Bundle class. @@ -180,9 +180,7 @@ public $depends = [ ]; ``` -> Note: You can only `depend` Assets with a higher or equal `$jsOption position`. - -In case you can't assure the registration order of a required module, but need to import the module, you can either require the module on demand within your module function or use the `lazy` flag of the require function. +In case you can't assure the module registration order of a required module, but need to import the module, you can either require the module on demand within your module function or use the `lazy` flag of the require function. The call to `require('anotherModule', true)` will return an empty namespace object in case the dependent module has not been registered yet. The actual module logic will be available after the dependent module is registered. @@ -207,7 +205,7 @@ humhub.module('example', function(module, require, $) { }); ``` -> Info: All core modules are appended to the head section of your document, so there should not be any dependency problem for those modules if you append your assets either at the begin or the end of the document body. +> Info: All core modules are appended to the head section of your document, so they should not be any dependency problem if you append your assets either at the begin or the end of the document body. ### Module Lifecycle @@ -218,7 +216,7 @@ A module runs through the following lifecycle (by the example of our `example` m 3. **Document Ready** 4. `humhub:beforeInitModule` 5. `humhub:modules:example:beforeInit` -6. Calling the modules `init` function with `isPjax = false` +6. Calling the modules `init` function 7. `humhub:modules:example:afterInit` 8. `humhub:afterInitModule` 9. `humhub:ready` @@ -227,7 +225,6 @@ A module runs through the following lifecycle (by the example of our `example` m 12. Calling the modules `unload` function 13. `humhub:modules:client:pjax:success` 14. Reinitialize all modules with `initOnPjaxLoad=true` by calling `init` with `isPjax = true` -15. `humhub:ready` ### Module Configuration @@ -238,7 +235,7 @@ humhub.module('example', function(module, require, $) { ... var myAction = function() { - if(module.config.showMore) { + if(module.config['showMore']) { // Do something } }; @@ -262,7 +259,7 @@ $this->registerJsConfig([ ); ``` -Setting configurations in Javascript: +Setting configurations in javascript: ```javascript // Set config values for multiple modules @@ -294,12 +291,12 @@ Beside the `config` array, the module instance furthermore provides a `text` uti //Configurate your text in your php view. $this->registerJSConfig('example', [ 'text' => [ - 'error.notallowed' => Yii::t('ExampleModule.views_example', 'You are not allowed to access example!'); + 'error.notallowed' => Yii::t('ExampleModule.views.example', 'You are not allowed to access example!'); ] ]); ``` -Access your text as +Access your text within your js module as this ```javascript module.text('error.notallowed'); @@ -344,7 +341,7 @@ module.log.error('error.default', new Error('xy'), true); The trace level of your module can be configured by setting the `traceLevel` configuration. If your module does not define an own trace level the log modules's traceLevel configuration will be used instread. In production mode the default log level is set to `INFO`, in dev mode its set to `DEBUG`. -> Info: Your module logger will try to resolve a given text key to a module or global text configuration. +> Info: Your module logger will try to resolve a given text key to a module or global text. > Info: The `module.log.success()` function will trigger a status bar update by default. diff --git a/protected/humhub/docs/guide/developer/live.md b/protected/humhub/docs/guide/developer/live.md new file mode 100644 index 0000000000..d84a7cd0c8 --- /dev/null +++ b/protected/humhub/docs/guide/developer/live.md @@ -0,0 +1,3 @@ +Live Updates +================= +(TBD) \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/migration-guide.md b/protected/humhub/docs/guide/developer/migration-guide.md new file mode 100644 index 0000000000..ea82c7dcf5 --- /dev/null +++ b/protected/humhub/docs/guide/developer/migration-guide.md @@ -0,0 +1,100 @@ +# Module Migration Guide + +Here you will learn how you can adapt existing modules to working fine with actually versions. + +## Migrate from 1.1 to 1.2 + +### Stream / Content Changes + +The models WallEntry and Wall were removed. So all corresponding methods like getFirstWallEntryId() are not longer available. +The stream handling is now handled directly by the Content model. Also all stream classes (widgets, actions) are moved into the humhub\modules\stream package. + + + +### File module changes + +Please refer the new [File Handling](files.md) documentation section for more details regarding the new file management API. + +- Deprecated widgets: + - humhub\modules\user\widgets\UserPicker (replaced with humhub\modules\user\widgets\UserPickerField) + - humhub\modules\space\widgets\Picker (replaced with humhub\modules\space\widgets\SpackePickerField) + - humhub\widgets\DataSaved (replaced with humhub\components\View::saved) +- Removed Content models 'attachFileGuidsAfterSave' attribute and handling +- Deprecated File model methods + - \humhub\modules\file\models\File::attachPrecreated + - \humhub\modules\file\models\File::getFilesOfObject + - \humhub\modules\file\models\File::getStoredFilePath + - \humhub\modules\file\models\File::getPreviewImageUrl + - \humhub\modules\file\models\File::attachPrecreated + - \humhub\modules\file\models\File::getFilename + - \humhub\modules\file\models\File::getInfoArray + - \humhub\modules\file\models\File::getMimeBaseType + - \humhub\modules\file\models\File::getMimeSubType + - \humhub\modules\file\models\File::getExtension +- Removed configuration option 'showFilesWidgetBlacklist' use WallEntry showFiles attribute instead. +- File models title attributes is not longer automatically populated with the filename when empty +- Moved file upload capabilities (UploadedFile) from File model to FileUpload model +- Moved file store content by attribute capabilities from File model to FileContent model +- Created UploadAction/DownloadAction classes + +### Javascript API changes + +TBD + +#### Pjax + TopNavigation: +Use + +public $topMenuRoute = '/dashboard/dashboard'; + +within your controller for pjax topmenu support. + +### Asset Handling changes + +TBD + + +## Migrate from 1.0 to 1.1 + +- Dropped unused space attribute "website" + +- ContentContainer Model Changes + - Removed canWrite method (now requires own implementation using permissions) + +- Content Model Changes + - Removed space_id / user_id columns - added contentcontainer_id + - Not longer validates content visibility (private/public) permissions + +- system_admin attribute in user table was removed + see [[humhub\modules\user\models\User::isSystemAdmin]] + +- Renamed space header settings menu dropdown class + from [[humhub\modules\space\modules\manage\widgets\Menu]] to [[humhub\modules\space\widgets\HeaderControlsMenu]] + +- Refactored settings system. see [Settings Documentation](settings.md) for more details. + Old settings api is still available in 1.1.x + +- Refactored user group system + +- New administration menu structure + +## Migrate from 0.20 to 1.0 + + +## Migrate from 0.12 to 0.20 + +**Important: This release upgrades from Yii1 to Yii2 Framework!** + +This requires an extensive migration of all custom modules/themes. +Find more details here: [HumHub 0.20 Migration](old/modules-migrate-0.20.md) + +## Migrate from 0.11 to 0.12 + +- Rewritten Search + +## Migrate from 0.10 to 0.11 + +No breaking changes. + +- Now handle ContentContainerController layouts, new option showSidebar +- New ContentAddonController Class +- New Wiki Parser / Editor Widget diff --git a/protected/humhub/docs/guide/developer/migration.md b/protected/humhub/docs/guide/developer/migration.md new file mode 100644 index 0000000000..c5fd4812fd --- /dev/null +++ b/protected/humhub/docs/guide/developer/migration.md @@ -0,0 +1,7 @@ +Module - Migration +================= +## Installation +## Uninstallation +## Compatibility + +(TBD) \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/models.md b/protected/humhub/docs/guide/developer/models.md new file mode 100644 index 0000000000..11780a71af --- /dev/null +++ b/protected/humhub/docs/guide/developer/models.md @@ -0,0 +1,87 @@ +(TBD:getPublishedUrl)Database and Models +==================== + +## Conventions + +- prefix your tables with the module id. e.g. example_foo +- singular table names +- use underscorce in fieldnames/attributes e.g. user_id + +## ActiveRecord (Model) + +To be able to provide persistent data a module has to implement model class derived from [[humhub\components\ActiveRecord]]. +Yii follows the concept of rich models, which means a model class can contain content in form of attributes as well as domain logic. +More information about the use of ActiveRecords is available in the [Yii2 guide](http://www.yiiframework.com/doc-2.0/guide-db-active-record.html). + +> Info: [[humhub\components\ActiveRecord]] is derived from [[yii\db\ActiveRecord]] and provides some automatic attribute settings as `created_by` and `crated_at` if the underlying table contains these fields. + +## Migrations + +See Yii 2.0 guide for more details about migrations [http://www.yiiframework.com/doc-2.0/guide-db-migrations.html](http://www.yiiframework.com/doc-2.0/guide-db-migrations.html). + +HumHub provides an enhanced Migration class [[humhub\components\Migration]] which provides the ability to rename class files. This is required because HumHub also stores some class names in database for Polymorphic relations. + +#### Usage + +- Create a module migration + `> php yii migrate/create example --migrationPath='@app/modules/polls/migrations'` +- Execute module migration + `> php yii migrate/up --migrationPath='@app/modules/polls/migrations'` +- Execute all migrations (including enabled modules) + `> php yii migrate/up --includeModuleMigrations=1` + +#### Uninstall + +There is a special migration file called 'uninstall.php' - which is executed after the module is uninstalled. +Use this drop created tables & columns. + +Example file: *migrations/uninstall.php* + +```php +dropTable('poll'); + $this->dropTable('poll_answer'); + $this->dropTable('poll_answer_user'); + } + + public function down() + { + echo "uninstall does not support migration down.\n"; + return false; + } + +} +``` + +## Data Integrity + +The integrity checker is a command which validates and if necessary repairs the application database. + +If you want to add own checking methods for your module to it, you can intercept the [[humhub\controllers\IntegrityController::EVENT_ON_RUN]] event. + +Example callback implementation: + +```php +public static function onIntegrityCheck($event) +{ + $integrityController = $event->sender; + $integrityController->showTestHeadline("Polls Module - Answers (" . PollAnswer::find()->count() . " entries)"); + + foreach (PollAnswer::find()->joinWith('poll')->all() as $answer) { + if ($answer->poll === null) { + if ($integrityController->showFix("Deleting poll answer id " . $answer->id . " without existing poll!")) { + $answer->delete(); + } + } + } +} +``` \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/module-change-behavior.md b/protected/humhub/docs/guide/developer/module-change-behavior.md new file mode 100644 index 0000000000..702a528f62 --- /dev/null +++ b/protected/humhub/docs/guide/developer/module-change-behavior.md @@ -0,0 +1,12 @@ +Change the behavior of core features +================= + +This guide describes possible ways for modules to change the behavior of core features. + +## Events +## Widgets +## Views +## Controller +## ActiveRecord +## Javascript +## Configuration diff --git a/protected/humhub/docs/guide/developer/modules-content.md b/protected/humhub/docs/guide/developer/modules-content.md new file mode 100644 index 0000000000..4d9a9d9c25 --- /dev/null +++ b/protected/humhub/docs/guide/developer/modules-content.md @@ -0,0 +1,186 @@ +Users +===== + +## ContentContainer + +A [[humhub\modules\content\models\ContentContainer|ContentContainer]] in HumHub is the base concept for assigning content entries to a specific container instance (user or space). +Each [[humhub\modules\content\models\ContentContainer|ContentContainer]] is assigned with an unique guid, which is used in controllers to identify the context of its actions. + +Currently there are two types of ContentContainer: + +- [[humhub\modules\user\models\User|User]] +- [[humhub\modules\space\models\Space|Space]] + +![Application Layers](images/contentContainerClassDiag.jpg) + +> Note: It's not possible to create own ContentContainer classes, yet. + +### ContentContainerController + +The [[humhub\modules\content\components\ContentContainerController|ContentContainerController]] class is extended by controllers working in the context of a specific [[humhub\modules\content\models\ContentContainer|ContentContainer]]. +A [[humhub\modules\content\components\ContentContainerController|ContentContainerController]] will automatically search for a **sguid** (Space) or **uguid** (User) request parameter in every request and will instantiate the corresponding [[humhub\modules\content\models\ContentContainer|ContentContainer]]. + +The [[humhub\modules\content\components\ContentContainerController|ContentContainerController]] provides common tasks like: + +- Automatic container loading based on URL +- Access checks +- Layout selection based on container type (User or Space) +- Create URL's for the given ContentContainer + +For example: + +```php +class ExampleController extends \humhub\modules\content\components\ContentContainerController +{ + public function actionIndex() + { + if ($this->contentContainer instanceof Space) { + //Space related stuff... + } + } +} +``` + +Url's pointing to a ContentContainer action should be created by using the `createUrl()` function +of your ContentContainer instance. This will add the required sguid or uguid to your request. + +```php + // Direct ContentContainer call + $space->createUrl('/module/controller/action'); + + // Within a ContentContainerController: + $this->contentContainer->createUrl('/module/controller/action'); +``` + +> Note: Requests for a [[humhub\modules\content\components\ContentContainerController|ContentContainerController]] action without providing a sguid or uguid parameter will fail! + +### ContentContainerActiveRecord + +Each ContentContainer class is derived from [[\humhub\modules\content\components\ContentContainerActiveRecord]]. +Beside others, this abstract class provides the following functionality: + +- [Permission Management](dev-permissions.md) `getPermissionManager()` +- Profile-/Banner-image access `getProfileImage()`, `getProfileBannerImage()` +- Rendering the container stream `getWallOut()` (see [Permission Management](dev-stream.md)) + +Profile image example: + +```php +//Get Profile Image Url +$profileImage = $space->getProfileImage(); +if($profileImage->hasImage()) { + $url = $profileImage->getUrl(); +} +``` + +### ContentContainerModule + +If a module should appear in the content containers module section, the module class must extend [[humhub\modules\content\components\ContentContainerModule]]. +A ContentContainerModule can be enabled or disabled for a specific ContentContainer. The calendar module, for example, can be enabled for a specific space or a specific user account. + +See the [[humhub\modules\content\components\ContentContainerModule]] class for a full list of options. + +Example of a modules `Module.php` file: + +```php +class Module extends \humhub\modules\content\components\ContentContainerModule +{ + + // Defines for which content container type this module is appropriate + public function getContentContainerTypes() + { + // This content container can be assigned to Spaces and User + return [ + Space::className(), + User::className(), + ]; + } + + // Is called when the whole module is disabled + public function disable() + { + // Clear all Module data and call parent disable + parent::disable(); + } + + // Is called when the module is disabled on a specific container + public function disableContentContainer(ContentContainerActiveRecord $container) + { + parent::disableContentContainer($container); + //Here you can clear all data related to the given container + } + + // Can be used to define a specific description text for different container types + public function getContentContainerDescription(ContentContainerActiveRecord $container) + { + if ($container instanceof Space) { + return Yii::t('MyModule.base', 'Description related to spaces.'); + } elseif ($container instanceof User) { + return Yii::t('MyModule.base', 'Description related to user.'); + } + } +``` +> Note: If you're working with content or other persistent data, make sure to delete container related data when the module is disabled on a contentcontainer. This can be archieved by overwriting the [[humhub\modules\content\components\ContentContainerModule::disableContentContainer]] function. + + +## Content + +TBD + +### ContentActiveRecord + +Each Content ActiveRecord (derived from [[\humhub\modules\content\components\ContentActiveRecord]]) is automatically linked to a [[humhub\modules\content\models\Content]] record via the *content* attribute. + +This Content record holds all neccessary information and provides common methods: + +- ContentContainer which the Content belongs to +- Meta Information (created_at, created_by, updated_at, updated_by) +- Wall Assignments / Methods +- Archiving / Pinning +- And more... + +If you're implementing an ActiveRecord based on [[humhub\modules\content\components\ContentContainerActiveRecord]] you need to implement the following abstract methods: + +- `getContentName()` - Returns the displayed name of the Content (e.g. Post or Poll) +- `getContentDescription()` - Returns a preview of the Content - which is used in Notifications for example. + +Example: + +```php + (TBD) + +``` + +#### Wall/Stream Output +(TBD) + +#### Querying Content + +If you're calling find() on a [[\humhub\modules\content\components\ContentActiveRecord]] instance you'll get a special [[\humhub\modules\content\components\ActiveQueryContent]] which provides additional methods to select content. + +- contentContainer($container) - Find content only inside a given container +- readable($user) - Return only user readable content +- ... + + +### Controller + +TBD + +## ContentAddon + +TBD + +- Always linked to particual Content, inherits access rules from it +- Examples: Like, File, Comment +- Can be nested (e.g. Container -> Content -> Addon (Comment) -> Addon (Like) + +### ActiveRecord + +TBD + +Provides access to the related content via *content *Attribute + +### Controller + +TBD diff --git a/protected/humhub/docs/guide/developer/modules-migrate.md b/protected/humhub/docs/guide/developer/modules-migrate.md index 3b4aa65fd5..277c24f752 100644 --- a/protected/humhub/docs/guide/developer/modules-migrate.md +++ b/protected/humhub/docs/guide/developer/modules-migrate.md @@ -1,8 +1,31 @@ -# Module Migration Guide +Module Migration Guide +====================== Here you will learn how you can adapt existing modules to working fine with actually versions. -## Migrate from 1.1 to 1.2 +Migrate from 1.2 to 1.3 +----------------------- + +### ContentContainer Controller + +The base controller attributes `autoCheckContainerAccess` and `hideSidebar` are not longer available. + +### Removed Deprecated + +- formatterApp Application Component (Yii::$app->formatterApp) + +### Queuing + +The queuing is now moved into an own module `humhub\modues\queue`. +The existing `humhub\components\queue\ActiveJob` is declared as deprecated and will be removed in 1.4. + +### Partial user deletion (Soft Delete) + +Added new user status (User::SOFT_DELETED). You can find more information here: [Users](modules-users.md) + + +Migrate from 1.1 to 1.2 +----------------------- ### Stream / Content Changes @@ -53,7 +76,8 @@ within your controller for pjax topmenu support. TBD -## Migrate from 1.0 to 1.1 +Migrate from 1.0 to 1.1 +----------------------- - Dropped unused space attribute "website" @@ -77,9 +101,11 @@ TBD - New administration menu structure -## Migrate from 0.20 to 1.0 +Migrate from 0.20 to 1.0 +------------------------ + ## Migrate from 0.12 to 0.20 **Important: This release upgrades from Yii1 to Yii2 Framework!** @@ -87,12 +113,17 @@ TBD This requires an extensive migration of all custom modules/themes. Find more details here: [HumHub 0.20 Migration](modules-migrate-0.20.md) -## Migrate from 0.11 to 0.12 + + +Migrate from 0.11 to 0.12 +------------------------- - Rewritten Search -## Migrate from 0.10 to 0.11 + +Migrate from 0.10 to 0.11 +------------------------- No breaking changes. - Now handle ContentContainerController layouts, new option showSidebar diff --git a/protected/humhub/docs/guide/developer/modules-users.md b/protected/humhub/docs/guide/developer/modules-users.md new file mode 100644 index 0000000000..104757c1cb --- /dev/null +++ b/protected/humhub/docs/guide/developer/modules-users.md @@ -0,0 +1,76 @@ +Users +===== + +Deleting Users +--------------------- + +Users can either be deleted with all their contributions (hard delete) or without, means only their personal/profile data will be deleted (soft delete) + +### Soft delete + +A common use cases for the soft delete option is: + +- Delete participation statuses (e.g. task assignments) +- Delete personal information and images + +You can manage the soft delete option by intercepting the event [[\humhub\modules\user\models\User::EVENT_BEFORE_SOFT_DELETE]]. + +Example 'config.php': + +```php + [ + [User::class, User::EVENT_BEFORE_SOFT_DELETE, [Events::class, 'onUserSoftDelete']], + // ... + ], + // ... +]; +?> +``` + +Example callback in your modules **Events** class: + +```php +public static function onUserSoftDelete(UserEvent $event) + { + $user = $event->user; + MyParticipations::deleteAll(['user_id' => $user->id]); + } +``` + + +### Hard delete + +The hard delete option will wipe all data in relation with the deleted user. +HumHub objects created by the user like comments, files, posts, notification or activities will automatically be removed with the user profile. + +Example 'config.php': + +```php + [ + [User::class, User::EVENT_BEFORE_DELETE, [Events::class, 'onUserDelete']], + // ... + ], + // ... +]; +?> +``` + +Example callback in your modules **Events** class: + +```php +public static function onUserDelete(Event $event) + { + $user = $event->sender; + MyRecord::deleteAll(['user_id' => $user->id]); + } +``` \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/modules.md b/protected/humhub/docs/guide/developer/modules.md new file mode 100644 index 0000000000..77aa767294 --- /dev/null +++ b/protected/humhub/docs/guide/developer/modules.md @@ -0,0 +1,247 @@ +Module - Getting Started +================= + +The following guide describes the basic module structure and extended module features as well as important considerations regarding your own custom module. + +## Before starting + +Before even starting the developement of a custom module, you first have to consider the following **module options**: + +- [Can my module be enabled on profile and/or space level?](modules.md#container-module) +- Does my module produce [stream entries](stream.md) or other [content](content.md)? +- Does my module produce [global](content.md#global-content) content or views (not bound to a profile/space) ? +- Does my module provide any kind of sidebar [snippet](snippet.md)? +- Do I need to [change the default behaviour](module-change-behavior.md) of some core components? +- Do I need specific [permissions](permissions.md) for my module? +- Does my module create any [notifications](notifications.md) or [activities](activities.md)? +- Should [guest](permissions.md#guests-access) users have access to some of my module views and functions? + +Furthermore you may have to consider the following **issues**: + +- [Module settings and configuration](settings.md) +- [Append my module to a specific navigation](module-change-behavior.md) +- [Client side developement](javascript-index.md) +- [Asset Management](assets.md) +- [Data Integrity](models.md#data-integrity) +- [Migrations and Uninstallation and Compatibility](migration.md) +- [Testing](testing.md) +- [File handling](files.md) +- [Events](events.md) +- [Translation](i18n.md) +- [Live UI updates](live.md) +- [Submodules](#submodules) +- [Security](security.md) +- [Theming](embedded-themes.md) + + +## Basic Module Structure + +Basically modules in HumHub are identical to [Yii2 modules](http://www.yiiframework.com/doc-2.0/guide-structure-modules.html). + +A very basic module consists of the following elements: + +``` + controllers/ - contains controller classes + migrations/ - contains database migration files and uninstall script + models/ - contains model classes + views/ - contains the modules view files + widgets/ - contains widget classes + Module.php - the main module class which can contain enable/disable logic for contentcontainer etc. + config.php - base module configuration. + module.json - module metadata +``` + +### config.php + +The `config.php` file enables automatic module loading and event configuration, without the need to manually modify the main application config, by returning an array including the following fields: + + +- **id** - Unqiue ID of the module (required) +- **class** - Namespaced classname of the module class (required) +- **namespace** - The namespace of your module (required) +- **events** - Array containing the modules event configuration (optional) +- **urlManagerRules** - Array of [URL Manager Rules](http://www.yiiframework.com/doc-2.0/yii-web-urlmanager.html#addRules()-detail) (optional) +- **modules** - Submodules (optional) + +Example `config.php` file: + +```php + 'example', + 'class' => 'johndoe\example\Module', + 'namespace' => 'johndoe\example', + 'events' => [ + ['class' => TopMenu::className(), 'event' => TopMenu::EVENT_INIT, 'callback' => ['johndoe\example\Module', 'onTopMenuInit']], + ] +]; +?> +``` + +> Note: Do not execute any code in the `config.php` since the result will be cached! + + +### Module.php + +The `Module.php` file contains the actual module class which should either extend the [[humhub\components\Module]] or [[humhub\modules\content\components\ContentContainerModule]] class. + + +The [[humhub\components\Module|Module]] class provides some basic module functions used for installing/uninstalling and retrieving metadata, whereas the [[humhub\modules\content\components\ContentContainerModule]] class has to be extended in case your module requires to be enabled on space or profile level. + +The Module class is responsible for: + +**Handling the enabling and disabling of the module** + +The modules `disable()` function is called if the module is disabled. + +```php +class Module extends \humhub\components\Module +{ + public function disable() + { + // Clear module related contentent etc... + foreach (MyContentModel::find()->all() as $model) { + $model->delete(); + } + + // Don't forget to call this!! + parent::disable(); + } +} +``` +>Note: The default implementation of `disable()` will clear some module data automatically as the module global and ContentContainer settings, profile/space module relations. + +#### Handling the enabling and disabling of this module for a given space or profile +See the [Container Module]() section for more information. + +#### Export Module Permissions +Module specific permissions are exported by means of the [[humhub\components\Module::getPermissions()]] function. See the [Permissions]() section for more information. + +#### Export Module Notification +Modules can export Notificaions in order to make them configurable in the notificaiton settings. +See the [Notifications]() section for more information. + +#### Module Assets and `$resourcesPath` +The [[humhub\components\Module::resourcesPath]] defines the modules resource directory, containing images, javascript files or other assets. + +See the [Module Assets]() section for more information. + +### module.json + +This file holds basic metadata which is for example used by the markeplace. + +Example `module.php` file: + +```json +{ + "id": "example", + "name": "My Example Module", + "description": "My testing module.", + "keywords": ["my", "cool", "module"], + "screenshots": ["assets/screen_1.jpg"], + "version": "1.0", + "humhub": { + "minVersion": "0.20" + } +} +``` + +- **id** - The module ID +- **name** - The modules name +- **description** - A short module description +- **keywords** - Array of significant keywords +- **screenshots** - Some screenshots for the marketplace +- **version** - Current module version +- **minVersion** - Defines the minimum HumHub core version this module version is compatible with. + +## Extended Module Structure + +The following structure contains some additional directories and files, which should be added for specific usecases or features. + + +``` + activities - activity classes + assets/ - asset bundle classes + components/ - component and services classes + controllers/ - see above + live/ - live event classes + jobs/ - queue job classes + messages/ - contains the modules message files + migrations/ - see above + models/ - see above + modules/ - contains any submodules + notifications/ - notification classes + permissions/ - permission classes + resources/ - contains web assets as javascript files or stylesheets + tests/ - module tests + views/ - see above + widgets/ - see above + Events.php - is often used for static event handlers + Module.php - see above + config.php - see above + module.json - see above +``` + +>Note: the extended module structure and it's directory names is just a recommendation. + +## Container Module + +In case your module can be enabled on space or profile level your `Module` class has to extend from [[humhub\modules\content\components\ContentContainerModule]]. You should extend this class if your module provides space or profile specific views or content. + +- The `getContentContainerTypes()` method defines for which ContentContainer type (space or profile) this module can be enabled. + +- The `disableContentContainer()` method is called when this module is disabled for a given ContentContainer (Space or Profile). + +- The `getContentContentContainerDescription()` method provides a general description of this module for the given ContentContainer. + +The following example module can be enabled on space and profile level: +```php +class Module extends \humhub\modules\content\components\ContentContainerModule +{ + + // Defines for which content container type this module can be enabled + public function getContentContainerTypes() + { + // This content container can be assigned to Spaces and User + return [ + Space::className(), + User::className(), + ]; + } + + // Is called when the whole module is disabled + public function disable() + { + // Clear all Module data and call parent disable + [...] + parent::disable(); + } + + // Is called when the module is disabled on a specific container + public function disableContentContainer(ContentContainerActiveRecord $container) + { + parent::disableContentContainer($container); + //Here you can clear all data related to the given container + } + + // Can be used to define a specific description text for different container types + public function getContentContainerDescription(ContentContainerActiveRecord $container) + { + if ($container instanceof Space) { + return Yii::t('MyModule.base', 'Description related to spaces.'); + } elseif ($container instanceof User) { + return Yii::t('MyModule.base', 'Description related to user.'); + } + } +} +``` + +> Note: If you're working with content or other persistent data, make sure to delete container related data when the module is disabled on a contentcontainer. This can be archieved by overwriting the [[humhub\modules\content\components\ContentContainerModule::disableContentContainer]] function. + +## Creating a Module Template with Gii + +(TBD) diff --git a/protected/humhub/docs/guide/developer/old/javascript.md b/protected/humhub/docs/guide/developer/old/javascript.md new file mode 100644 index 0000000000..78b916aa30 --- /dev/null +++ b/protected/humhub/docs/guide/developer/old/javascript.md @@ -0,0 +1,335 @@ +Javascript API +======= + +Since version 1.2, HumHub provides a module based Javascript API within the `humhub` namespace. +Instead of embeding inline script blocks into your view files, it's highly recommended to use the new module system for your modules frontend logic. + +## Modules + +### Module Asset + +Module script files should reside within the `resources/js` folder of your humhub module and should ideally be appended at the bottom of your document. This can be achieved by using [Asset Bundles](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html). + + +```php +namespace humhub\modules\example\assets; + +use yii\web\AssetBundle; + +class ExampleAsset extends AssetBundle +{ + // You can also use View::POS_BEGIN to append your scripts to the beginning of the body element. + public $jsOptions = ['position' => \yii\web\View::POS_END]; + public $sourcePath = '@example/resources'; + public $js = [ + 'js/humhub.example.js' + ]; +} +``` + +> Note: Make sure to add your assets after the core scripts, which are added within the html head. + +> Note: Your Asset Bundle should reside in the `assets` subdirectory of your module. + +In your view you can register your Asset Bundle by calling + +```php +\humhub\modules\example\assets\ExampleAsset::register($this); +``` + +Where `$this` is the view instance. More infos about the use of Asset Bundles are available in the [Yii Guide](http://www.yiiframework.com/doc-2.0/guide-structure-assets.html). + +### Module Registration + +Modules are added to the `humhub.modules` namespace by calling the `humhub.module` function. + +```javascript +humhub.module('example', function(module, require, $) { + ... +}); +``` +Example of a submodule: + +```javascript +humhub.initModule('example.mySubmodule', function(module, require, $) { +... +} +``` + +The first argument of the `humhub.module` function defines the module id, which should be unique within your network. The second argument is the actual module function itself. + +> Note: You should use an unique namespace for your custom modules as `myproject.mymodule` otherwise it may interfere with other modules. + +Your module function will be called with the following arguments: + +1. `module` - Your module instance, used for exporting module logic and accessing module specific utilities +2. `require` - Used for injecting other modules. +3. `$` - jQuery instance. + +##### Module Exports + +Module functions and attributes can only be accessed outside of the module if they are exported, either by directly appending them to the `module` object or by calling `module.export`. + +```javascript +humhub.module('example', function(module, require, $) { + + // private function + var private = function() { /* ... */ } + + // direct export of public function + module.publicFunction = function() {/* ... */} + + // another public function exported later + var publicTwo = function() { /* ... */} + + // Exports multiple values + module.export({ + publicTwo: publicTwo, + publicThree: function() {/** Test function **/} + }); +}); +``` +##### Module Initialization + +Your module's initialization logic can be implemented by exporting an `init` function. This function will automatically be called after the page is loaded. + +By default this function is only called once after a full page load (or directly after the registration if it was loaded per ajax). If your module requires a reinitialization also after Pjax page loads, your module has to set the `initOnPjaxLoad` setting. + +```javascript +module.initOnPjaxLoad = true; + +var init = function($pjax) { + // Do some global initialization work, which needs to run in any case + if($pjax) { + // Runs only after a pjax page load + } else { + // Runs only after fresh page load + } +} + +module.export({ + init: init +}); +``` + +##### Module Unload + +For the purpose of cleaning up module related dom nodes etc. there is also an `unload` function, which is called before each Pjax page load. This function is mainly used to remove obsolete dom nodes in order to prevent memory leaks, remove obsolete dom listeners, or clear some module data. + +```javascript +var unload = function($pjax) { + $('.moduleResidues').remove(); +} + +module.export({ + unload: unload +}); +``` + +##### Module Dependencies + +Other modules can be injected into your module by using the `require` function. + +```javascript +// Import of the core client module. +var client = require('client'); + +//Calling myFunction within another module +require('example').myFunction(); + +//Calling myFunction within another module (full path) +require('humhub.modules.example').myFunction(); + +//Also a valid call +require('modules.example').myFunction(); + +//Calling myFunction outside of a module +humhub.modules.example.myFunction(); +``` + +> Note: You should only require modules at the beginning of your own module, if you are sure the required module is already registered. + +If your module requires other modules, which are not part of the core you can ensure the order by means of the `$depends` attribute of your Asset Bundle: + + +```php +public $depends = [ + 'humhub\modules\anotherModule\assets\AnotherModuleAsset' +]; +``` + +If you can't assure the module registration order for another module, but need to require the module, you can either require it within your module function or use the `lazy` flag of the require function. + +The call of `require('anotherModule', true)` will return an empty namespace object, in case the module was not registered yet. The module logic will be available after the registration of the dependent module. + +>Note: When using the `lazy` flag, you can't assure the required module will be initialized within your own module's `init` logic. + +```javascript +humhub.initModule('example', function(module, require, $) { + // We can't ensure the initial logic of module2 + var module2 = require('module2', true); + + // at this point module2 might be empty + + var myFunction = function() { + // myFunction should only be used outside of the init logic + module2.executeSomeFunction(); + } +}); +``` + +>Info: Since core modules are appended to the head section of your document, there shouldn't be any dependency problem. + +### Module Configuration + +If you need to transfer values as texts, settings or urls from your php backend to your frontend module, you can use the `module.config` array which is automatically available within your module as in the following example: + +```javascript +humhub.initModule('example', function(module, require, $) { + + var myAction = function() { + if(module.config.showMore) { + // Do something + } + + }; +}); +``` + +In your view you can set the module configuration as follows + +```php +// Single module +$this->registerJsConfig('example', ['showMore' => true]); + +// Multiple modules +$this->registerJsConfig([ + 'example' => [ + 'showMore' => true + ], + 'anotherModule' => [ + ... + ] +); +``` + +Setting configurations in javascript: + +```javascript +//Set config values for multiple modules, +humhub.config.set({ + 'myModule': { + 'myKey': 'value' + }, + 'moduleXY': { + ... + } +}); + +//Set single value +humhub.config.set('myModule', { + 'myKey': 'value' +}); + +//You can also call +humhub.config.set('myModule', 'myKey', 'value'); +``` + +>Note: Since the configuration can easily be manipulated, you should not set values which can compromise the security of your application. + +> TIP: Module setter are normally called within views or widgets to inject urls or translated text for user feedback or modals. + +### Module Texts + +Beside the configuration addition, the module instance does furthermore provide a `module.text` function for easily accessing texts of your configuration. + +Example of an error text. + +```php +//Configurate your text in your php view. +$this->registerJsConfig([ + 'example' => [ + 'showMore' => true, + 'text' => [ + 'error.notallowed' => Yii::t('ExampleModule.views.example', 'You are not allowed to access example!'); + ] + ] +); +``` + +Access your text within your module function as this + +```javascript +module.text('error.notallowed'); + +// which is a short form of: +module.config['text']['error.notallowed']; +``` + +### Module Log + +Your module is able to create module specific log entries by using the `module.log` object of your module instance. +The log object supports the following log level functions: + +1. *trace* - For detailed trace output +2. *debug* - For debug output +3. *info* - Info messages +4. *success* - Used for success info logs +5. *warn* - Warnings +6. *error* - For error messages +7. *fatal* - Fatal errors + +All log functions accept up to three arguments: + +1. The actual message +2. Details about the message (or errors in case of warn/error/fatal) +3. A setStatus flag, which will trigger a global `humhub:modules:log:setStatus` event. This can be used to give user-feedback (status bar). + +Instead of an actual message, you can also just provide a text key as the first argument. +The following calls are valid: + +```javascript +// Log config text 'error.notallowed' and give user feedback. +module.log.error('error.notallowed', true); + +// In the following example we received an error response by our humhub.modules.client. The response message will try to resolve a default +// message for the status of your response. Those default messages are configured in the core configuration texts. +module.log.error(response, true); + +// The error.default text message is available through the configuration of the log module see humhub\widgets\JSConfig +module.log.error('error.default', new Error('xy'), true); +``` + +> Info: Your module logger will try resolving your message string to a module or global text. + +> Note: The success log will by default trigger a status log event. + +The trace level of your module can be configured by setting the `traceLevel` of your module configuration. +If your module does not define an own trace level the log modules's traceLevel configuration will be used. + +> Info: In production mode the default log level is set to `INFO`, in dev mode its set to `DEBUG`. + +> Note: If you change the `traceLevel` of a module at runtime, you'll have to call `module.log.update()`. + +## Core Modules +### Config Module + +Beside the `module.config` utility you can also use the global configuration as follows + +```javascript +// Retrieves the whole config object of 'myModule' +var moduleConfig = require('config').get('myModule'); +var myValue = config['myKey']; + +//Single value getter with default value +var myValue = humhub.config.get('myModule', 'myKey', 'myDefaultValue'); +``` + +With `humhub.config.is`, you are able to check if a value is true + +```javascript +//Check if the configkey enabled is true, default false +if(humhub.config.is('myModule', 'enabled', 'false')) { + ... +} +``` \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/old/modules-migrate-0.20.md b/protected/humhub/docs/guide/developer/old/modules-migrate-0.20.md new file mode 100644 index 0000000000..6269001d6a --- /dev/null +++ b/protected/humhub/docs/guide/developer/old/modules-migrate-0.20.md @@ -0,0 +1,182 @@ +# Module Migration Guide to HumHub 0.20 + +This document briefly describes the module api changes in HumHub 0.20. + +Since HumHub 0.20 is now based on Yii 2 you also need to migrate your modules to it. + +See more information about Yii 2.0 here: +- [Yii: Upgrading from Version 1.1](http://www.yiiframework.com/doc-2.0/guide-intro-upgrade-from-v1.html) +- [Yii2 Migration Notes](dev-migrate-0.20-yii2.md) + +## Migration + +### General + +- Namespace classes e.g. module\example or create own Namespace +- Migrate to Yii 2.0 (Controllers, Models, Views, Assets, ...) +- Migrate to HumHub Namespaced classes +- Raname e.g. ExampleModule.php && ExampleEvents.php to Module.php / Events.php +- Changed former autostart.php to config.php: [config.php](dev-module-index.md#configphp) +- Migrate database (see below) + +### Database / Migrations + +Also the existing migration script needs to use Namespaces now. + +Because also class names of your module may stored in database (e.g. for Activities, Likes, Notification, ...) +It's neccessary to create a new migration derived from [[humhub\components\Migration]] which uses **renameClass** method class to fix these records. + +Example Migration: + +```php +renameClass('WikiPage', WikiPage::className()); + $this->renameClass('WikiPageRevision', WikiPageRevision::className()); + } + + public function down() + { + echo "m150705_081309_namespace cannot be reverted.\n"; + + return false; + } + +} +``` + +### Content & ContentAddon + +getContentTitle is now divided into + +- getContentTitle +- getContentPreview + + +### autostart.php + +TBD + +- Renamed to config.php +- Removed imports +- Return array +- Namespaces + +### Urls + +[[\humhub\modules\content\components\activerecords\ContentContainer::createUrl]] (Space/User) still provides the method createUrl to build URLs in container context (sguid/uguid). + +Use [http://www.yiiframework.com/doc-2.0/yii-helpers-url.html](http://www.yiiframework.com/doc-2.0/yii-helpers-url.html) for other URLs. + +### Activities + +Please recreate Activities as described here: [[dev-module-activities.md]] + +Since 0.20 there is an automatic "created" Activity for Content. + +To migrate existing Activities to the new Scheme you need to create an migration. + +Example Migration: + +```php +// Rename Custom Activities +$this->update('activity', ['class' => 'exampleModule\activities\MyActivity'], ['class' => 'OldActivityName']); + +// Rename own "Created" Activities to core +$this->update('activity', ['class' => 'humhub\modules\content\activities\ContentCreated', 'module' => 'content'], ['class' => 'PollCreated']); +``` + +### Assets + +The asset/resources folder will not longer automatically published. + +Find more details about assets here: +http://www.yiiframework.com/doc-2.0/guide-structure-assets.html + +The default path for module resources (javascripts, images, css, ...) was changed from asset to resources. ('/modules/example/resources'). +Also all HumHub module related files like Module Image or Screenshots should be located there. + +You can change this path back to 'assets' by overwriting the 'resourcesPath' Attribute in your Module class. + +e.g. + +```php +class Module extends \humhub\components\Module +{ + + public $resourcesPath = 'assets'; +``` + +### Module Configuration + +The URL handling to configure your module in **Administration -> Module** has changed. + +The method "getConfigUrl" in Module class is not longer used. +Set configRoute attribute instead. + +e.g. + +```php +class Module extends \humhub\components\Module +{ + public $configRoute = '/example/admin/config'; +``` + +## (Incomplete) List of changed Helpers/Widgets: + +#### EnrichText + +```php +// New +echo humhub\widgets\RichText::widget(['text' => $text]); + +// Old +echo HHtml::enrichTest($text); +``` + +#### TimeAgo +```php +// New +echo \humhub\widgets\TimeAgo::widget(['timestamp' => $time]); + +// Old +echo HHtml::timeAgo($time); +``` + +#### PostLink +```php +// New +echo Html::a($label, $url, ['data-method'=>'POST']); + +// Old: +echo HHtml::postLink(...); +``` + +#### AjaxButton/Submit/... +```php +echo \humhub\widgets\AjaxButton::widget([ + 'label' => "Save", + 'ajaxOptions' => [ + 'type' => 'POST', + 'beforeSend' => new yii\web\JsExpression('function(){ setModalLoader(); }'), + 'success' => new yii\web\JsExpression('function(html){ $("#globalModal").html(html); }'), + 'url' => $space->createUrl('/space/admin/crop-image'), + ], + 'htmlOptions' => [ + 'class' => 'btn btn-primary' + ] +]); +``` + +#### Yii::app()->input->stripClean + +Stripclean is not longer available. use Html::encode() user input on output _and_ create validators. diff --git a/protected/humhub/docs/guide/developer/old/modules-migrate-yii1.md b/protected/humhub/docs/guide/developer/old/modules-migrate-yii1.md new file mode 100644 index 0000000000..44c7c24bec --- /dev/null +++ b/protected/humhub/docs/guide/developer/old/modules-migrate-yii1.md @@ -0,0 +1,41 @@ +[Back to 0.20 Migration](modules-migrate-0.20.md) + +# Yii2 Migration Notes + +See Yii Migration Guide: [http://www.yiiframework.com/doc-2.0/guide-intro-upgrade-from-v1.html](http://www.yiiframework.com/doc-2.0/guide-intro-upgrade-from-v1.html) + +Notes: + +- Use Namespaces! +- Yii::app() -> Yii::$app +- Use [] instead of array() - Optional +- Model: Validator + - Use array for multiple attributes + - Validator changes Numeric->Integer ... + - String validator doesn't allow Integer Types (cast!) + - Scenarios now in separate methods secenarios() + - User::model()->findByPk($idy); -> User::findOne(['id'=>$id); + - Check beforeSave/afterSave when overwriting they may have parameters + - Better use $insert when available instead of $this->isNewRecord + - Make tableName method static + +- Views: + - ClientScript removed e.g. Yii::app()->clientScript->registerScriptFile + - New Widget calls WidgetClass::widget([options]) & echo it! +- Controllers + - Always return render action (also Widgets) + - camel case actions e.g. actionEditItem new Url: edit-item + - Easier: JSON Output + Yii::$app->response->format = 'json'; return $json; +- createUrl removed -> Url::to() +- Behaviors + - $this->getOwner() replaced by $this->owner +- Html (CHtml) + - reduced (e.g. no AjaxButton - use: \humhub\widgets\AjaxButton instead + - Html::link -> confirm changed to data-confirm + - + + + + + diff --git a/protected/humhub/docs/guide/developer/overview.md b/protected/humhub/docs/guide/developer/overview.md index bb354d10ee..3b5ee64355 100644 --- a/protected/humhub/docs/guide/developer/overview.md +++ b/protected/humhub/docs/guide/developer/overview.md @@ -1,48 +1,40 @@ Overview ======== -The main power of the HumHub platform lies in its flexibility and extensibility through modules and configuration of runtime behaviour. -HumHub is written mostly in PHP and is based on the [Yii Framework](http://www.yiiframework.com/). Other languages used throughout the platform are *JavaScript*, *HTML*, *SQL* and *CSS*. +HumHub provides a powerful modular platform based on the [Yii2 Framework](http://www.yiiframework.com). +The modular nature of the HumHub platform allows you to add new features or change existing core features by means of +custom modules. -HumHub uses a [Model-View-Controller (MVC)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) based software architecture and utilizes technologies such as [Yii2](http://www.yiiframework.com/), [jQuery](https://jquery.com/), [Bootstrap](http://getbootstrap.com/) and [Less](http://lesscss.org/), to name a few. +Other languages used throughout the platform, besides PHP, are JavaScript, HTML, SQL and CSS/Less. +HumHub is based on the Model-View-Controller (MVC) pattern and uses frontend technologies such as [jQuery](https://jquery.com/), [Bootstrap](http://getbootstrap.com/) and [Less](http://lesscss.org/). + +In this guide, you will find all the necessary information to customize your HumHub installation and implement your own modules. + +As HumHub is based on the [Yii 2.0 PHP Framework](http://www.yiiframework.com/) make sure you're also familiar with the basic concepts this framework: + +- [The Definitive Guide to Yii 2.0](http://www.yiiframework.com/doc-2.0/guide-index.html) + +## HumHub Core ![Application Layers](images/appLayer.svg) -> Note: Since HumHub v1.2 the minimum PHP version is 5.6 +The HumHub core consists of a set of core components, modules, widgets, helpers and views. +HumHub extends several Yii base components such as: -As HumHub is based on the [Yii 2.0 PHP Framework](http://www.yiiframework.com/) make sure you're also familiar with the concepts of this framework in order to beeing able to write own modules or extend the core platform. + - [[humhub\components\ActiveRecord|ActiveRecord]] + - [[humhub\components\Application|Application]] + - [[humhub\components\Controller|Controller]] + - [[humhub\components\Migration|Migration]] + - [[humhub\components\Module|Module]] + - [[humhub\components\ModuleManager|ModuleManager]] + - [[humhub\components\Request|Request]] + - [[humhub\components\Theme|Theme]] + - [[humhub\components\User|User]] + - [[humhub\components\View|View]] + - [[humhub\components\Widget|Widget]] + - and more... -Here are some recommendet resources for learning Yii: - -- [The Definitive Guide to Yii 2.0](http://www.yiiframework.com/doc-2.0/guide-index.html) -- [Yii 2.0 Community Cookbook](https://yii2-cookbook.readthedocs.io/) - -The HumHub core platform consists of several modules as well as extended Yii components: - -**Core Components:** - - - [[humhub\components\ActiveRecord]] - - [[humhub\components\Application]] - - [[humhub\components\console\Application]] - - [[humhub\components\AssetManager]] - - - [[humhub\components\Controller]] - - [[humhub\components\Migration]] - - [[humhub\components\Module]] - - [[humhub\components\ModuleManager]] - - [[humhub\components\Request]] - - [[humhub\components\Response]] - - [[humhub\components\SettingsManager]] - - [[humhub\components\Theme]] - - [[humhub\components\SocialActivity]] - - [[humhub\components\View]] - - [[humhub\components\Widget]] - - **humhub\components\i18n** - - **humhub\components\mail** - - **humhub\components\queue** - - **humhub\components\rendering** - - **humhub\components\validators** - -**Core Modules:** +and consists of the following core modules: - **activity:** User/Space activities - **admin:** Responsible for admin/configuration related issues @@ -51,14 +43,29 @@ The HumHub core platform consists of several modules as well as extended Yii com - **dashboard:** Dashboard related functionality - **directory:** Directory related functionality - **file:** Basic file module for accessing the filesystem - - **friendship:** Friendship system functionality + - **friendship:** User friendship module - **installer:** HumHub installer module - **like:** Content addon for likes - - **live:** Live frontend update functionality - - **notification:** User notifications (e.g. e-mail, web) + - **live:** Used for frontend live updates + - **notification:** User Notifications - **post:** Simple user-post related functionality - **search:** Luceene Search Module - **space:** Space related functionality - - **stream:** Content stream related functionality - - **tour:** HumHub user-guide tour - - **user:** User and authentication + - **stream:** Content streams and walls + - **tour:** HumHub user-guide + - **user:** Basic user module + + ### Application structure + + ``` + assets/ - contains published asset files + protected/ - protected files as sources, modules, configuration etc. + protected/config - dynamic and user configuration + protected/humhub - humhub core directory + protected/modules - default directory for non core modules + protected/runtime - runtime files as cache, search index, logs etc. + protected/vendor - third party libraries loaded by composer + static/ - static asset files as production assets core javascript/less files etc. + themes/ - contains standalone themes (not bundled within a module) + uploads/ - uploaded files profile images etc. + ``` diff --git a/protected/humhub/docs/guide/developer/permissions.md b/protected/humhub/docs/guide/developer/permissions.md index 1bf203fac2..03e0f24b95 100644 --- a/protected/humhub/docs/guide/developer/permissions.md +++ b/protected/humhub/docs/guide/developer/permissions.md @@ -205,7 +205,7 @@ In the following, we'll show some more use cases for the [[humhub\components\beh return [ 'acl' => [ 'class' => \humhub\components\behaviors\AccessControl::className(), - 'adminOnly' => true, + 'adminOnly' => true ] ]; @@ -213,23 +213,26 @@ return [ return [ 'acl' => [ 'class' => \humhub\components\behaviors\AccessControl::className(), - 'guestAllowedActions' => ['index'], + 'guestAllowedActions' => ['index'] 'rules' => [ - ['permissions' => SpecialPermission::className(), 'actions' => ['secret']], - ], - ], + ['permissions' => SpecialPermission::className(), 'actions' => ['secret']] + ]; + ] ]; // Combined rules: Every action is only granted for users with SpecialPermission except 'secret' action, which is accessible by SpecialPermission and SpecialAdminPermission users. return [ 'acl' => [ 'class' => \humhub\components\behaviors\AccessControl::className(), - 'guestAllowedActions' => ['index'], + 'guestAllowedActions' => ['index'] 'rules' => [ - ['permissions' => SpecialPermission::className()], - ['permissions' => [SpecialPermission::className(), SpecialAdminPermission::className()], 'actions' => ['secret']], - ], - ], + ['permissions' => SpecialPermission::className()] + ['permissions' => [SpecialPermission::className(), SpecialAdminPermission::className()], 'actions' => ['secret']] + ]; + ] ]; ``` + +## Guest Access +(TBD) \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/secuirty.md b/protected/humhub/docs/guide/developer/secuirty.md new file mode 100644 index 0000000000..15da7cec0c --- /dev/null +++ b/protected/humhub/docs/guide/developer/secuirty.md @@ -0,0 +1,4 @@ +Security +================= + +(TBD) \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/settings.md b/protected/humhub/docs/guide/developer/settings.md new file mode 100644 index 0000000000..f73353fe8a --- /dev/null +++ b/protected/humhub/docs/guide/developer/settings.md @@ -0,0 +1,75 @@ +Settings Manager +================ + +The SettingsManager allows you to easily store key/value based configuration settings +based on module components and also optionally bound to a contentcontainer (e.g. users or spaces). + +If you need to categorize key names, use this syntax: category.subcategory.camelCaseKeyName + +The SettingsManager component is automatically added all to humhub\components\Module classes. + +Module settings +--------------- + +Get desired module / application instance: + +```php +$module = Yii::$app; +// or +$module = Yii::$app->getModule('polls'); +// or +$module = $controller->module; +``` + + +Create or update existing setting in settings manager: + +```php +$module->settings->set('key', $value); +``` + + +Get value of setting manager: + +```php +$value = $module->settings->get('key'); +``` + + +Delete setting: + +```php +$module->settings->delete('key'); +// or +$module->settings->set('key', null); +``` + + +ContentContainer related settings +--------------------------------- + +If you want to store settings related to an user or space - use the ContentContainerSettingsManager: + +```php +$module->settings->contentContainer($user)->get('key'); +$module->settings->contentContainer($user)->set('key', $value); +$module->settings->contentContainer($user)->delete('key'); +``` + + +Shortcuts for currently logged in user settings: + +```php +$module->settings->user()->get('key'); +$module->settings->user()->set('key', $value); +$module->settings->user()->delete('key'); +``` + +Shortcuts for current space settings: +Note: This is only available if current controller is instance of ContentContainerController. + +```php +$module->settings->space()->get('key'); +$module->settings->space()->set('key', $value); +$module->settings->space()->delete('key'); +``` diff --git a/protected/humhub/docs/guide/developer/snippet.md b/protected/humhub/docs/guide/developer/snippet.md new file mode 100644 index 0000000000..966f2156fa --- /dev/null +++ b/protected/humhub/docs/guide/developer/snippet.md @@ -0,0 +1,6 @@ +Snippets +================= + +Snippets are self contained panels which can be added to the sidebar of for exaple the space or dashboard layout. + +(TBD) \ No newline at end of file diff --git a/protected/humhub/docs/guide/developer/stream.md b/protected/humhub/docs/guide/developer/stream.md index 4f02e90e85..7b02ac6bd6 100644 --- a/protected/humhub/docs/guide/developer/stream.md +++ b/protected/humhub/docs/guide/developer/stream.md @@ -1,18 +1,136 @@ -# Streams / Walls +Content Streams +================= +Streams are used to asynchronously load batches of content entries which can be filtered or sorted. +The stream concept is used for example in _space and profile walls_, the _dashboard_ and +_activity stream_. -TBD +Custom modules can use own streams for example to filter content by a specific type, or other custom +filters. -- Define Streaming/Wall +### Stream Channel -You can also implement Creanown Stream/Wall output for your module content only. +The `stream_channel` attribute of a [[humhub\modules\content\models\Content]] entry defines the relation of this content to +a specific type of stream. The `default` stream channel for example is used by _space/profile_ and _dashboard_ +streams and the `activity` stream channel is exclusively used in activity streams. -Example Implementations: +The stream channel of your content type can be overwritten by setting the [[humhub\modules\content\components\ContentActiveRecord::streamChannel|ContentActiveRecord::streamChannel]] attribute. -- Tasks -- Polls +You can consider the following stream channel options for your own [[humhub\modules\content\components\ContentActiveRecord|ContentActiveRecord]]: -Of course your modules Content implementation needs to provides a WallEntry widget. See Content Section for more details. +- `default` stream channel will include your content to default _space/profile walls_ and the _dashboard_. You are still able to create a custom stream view which filters content by type. +- `null` will exclude the content from the default streams +- Use a custom stream channel if you exclusively want your content to be included in your own custom stream (similar to activity concept). +> Note: A custom stream channel should be unique, so choose a meaningful name preferably with module prefix. +### WallEntry Widget + +A [[humhub\modules\content\widgets\WallEntry|WallEntry widget]] is responsible for rendering the individual stream entries +of a stream and is defined by [[humhub\modules\content\components\ContentActiveRecord::wallEntryClass|ContentActiveRecord::wallEntryClass]]. + +The following example shows a very basic WallEntry widget implementation. + +> Note: By default your WallEntry view only have to render the actual content, the default WallEntry layout is available in `@humhub/modules/content/widgets/views/wallEntry.php` + +```php +class WallEntry extends \humhub\modules\content\widgets\WallEntry +{ + public function run() + { + return $this->render('wallEntry', [ + 'model' => $this->contentObject + ]); + } +} +``` + +wallEntry.php: +```php +
+ title ?> + myContent ?> + ... +
+``` + +The WallEntry widget will be provided with a [[humhub\modules\content\widgets\WallEntry::contentObject|contentObject]] which holds the +[humhub\modules\content\components\ContentActiveRecord|ContentActiveRecord]] model to be rendered. + +Your [[humhub\modules\content\widgets\WallEntry|WallEntry]] class can also set the following attributes: + + - [[humhub\modules\content\widgets\WallEntry::editRoute|editRoute]] defines an edit route to your edit action which will be used to render an edit link (see WallEntryControls section) + - [[humhub\modules\content\widgets\WallEntry::editMode|editMode]] defines the way the edit action is triggered (see WallEntryControls section) + - [[humhub\modules\content\widgets\WallEntry::wallEntryLayout|wallEntryLayout]] defines the layout used to embed the result of `render()`, by default you only have to care about rendering the content section of your WallEntry + + +#### WallEntryControls + +The default WallEntry layout contains a context menu with content actions like `edit`, `delete`, `archive` etc. +This menu can be manipulated by overwriting the [[humhub\modules\content\widgets\WallEntry::getContextMenu()|getContextMenu()]] function and +or use the [[humhub\modules\content\widgets\WallEntry::controlsOptions|controlsOptions]] property as in the following example. + +By setting the [[humhub\modules\content\widgets\WallEntry::editRoute|editRoute]] we automatically add an edit link to our WallEntryControls in +case the current user is allowed to edit the content. The type of the edit action is defined by the [[humhub\modules\content\widgets\WallEntry::editMode|editMode]]. + +There are the following edit modes available: + + - `EDIT_MODE_MODAL` the response of `editRoute` will be loaded into a modal. + - `EDIT_MODE_INLINE` the response of `editRoute` will be embeded into the WallEntry content. + - `EDIT_MODE_NEW_WINDOW` the page response of `editRoute` will be fully loaded. + +```php +class WallEntry extends \humhub\modules\content\widgets\WallEntry +{ + public $editRoute = "/my-module/entry/edit"; + + public $editMode = self::EDIT_MODE_MODAL; + + // Will prevent the default DeleteLink and always add a MySpecialLink + $this->controlsOptions = [ + 'prevent' => [\humhub\modules\content\widgets\DeleteLink::class], + 'add' => [MySpecialLink::class] + ]; + + //... + + public function getContextMenu() + { + $result = parent::getContextMenu(); + + // Only add a CloseLink if the user is allowed to edit the content. + if($this->contentObject->content->canEdit()) { + $this->addControl($result, [CloseLink::class, ['model' => $this->contentObject], ['sortOrder' => 200]]); + } + + return $result; + ] +} +``` + +CloseLink example: + +```php +class CloseLink extends humhub\modules\content\widgets\WallEntryControlLink +{ + public $model; + + public function init() + { + if($this->model->closed) { + $this->label = Yii::t('MyModule.base', 'Reopen'); + $this->icon = 'fa-check'; + } else { + $this->label = Yii::t('MyModule.base', 'Close'); + $this->icon = 'fa-times'; + } + + $this->options = [ + // set some further html options + ]; + + parent::init(); + } +} +``` ## Create own Module Content Stream @@ -23,24 +141,14 @@ Derived from [[humhub\modules\content\components\actions\ContentContainerStream] Example: ```php -activeQuery->andWhere(['content.object_model' => Poll::className()]); } - } - ``` Specify Action in Controller diff --git a/protected/humhub/docs/guide/developer/testing.md b/protected/humhub/docs/guide/developer/testing.md index 1c552da1bd..928fa60417 100644 --- a/protected/humhub/docs/guide/developer/testing.md +++ b/protected/humhub/docs/guide/developer/testing.md @@ -1,6 +1,17 @@ Testing (since v1.2) ==================== +## Testing basics +Testing in Humhub/Yii extends the paradigm of unit testing. I.e. testing models and controllers is complemented by functional and acceptance tests. Acceptance tests cover scenarios from a user's perspective: Opening the browser, accessing the site, click links and buttons etc. This behaviour can be simulated. Therefore a webserver as well as an automated browser instance are necessary (see below). + +Functional tests are similar to acceptance tests but run without a running webserver. + +Codeception allows integrated testing with acceptance, functional and unit tests. + +*ATTENTION: Some of the test libraries are developed for use with PHP 7 only* + +Details: [Codeception](http://codeception.com/docs/01-Introduction) + ## Test Environment Setup - Install codeception ([http://codeception.com/install](http://codeception.com/install)) @@ -13,7 +24,7 @@ composer global require "codeception/codeception=2.0.*" "codeception/specify=*" - Create test Database: ``` -CREATE DATABASE `humhub_test` CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE DATABASE `humhub_test` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` - Configure database access: @@ -21,7 +32,7 @@ CREATE DATABASE `humhub_test` CHARACTER SET utf8 COLLATE utf8_general_ci; Configure the database connection for your test environment in `@humhub/tests/config/common.php`: - + ``` 'components' => [ 'db' => [ 'dsn' => 'mysql:host=localhost;dbname=humhub_test', @@ -30,36 +41,33 @@ Configure the database connection for your test environment in `@humhub/tests/co 'charset' => 'utf8', ], ] - +``` - Run Database Migrations: -```cd protected/humhub/tests/codeception/bin``` +`cd protected/humhub/tests/codeception/bin` -```php yii migrate/up --includeModuleMigrations=1 --interactive=0``` +`php yii migrate/up --includeModuleMigrations=1 --interactive=0` -```php yii installer/auto``` +`php yii installer/auto` >Note: You'll have to run the migrations for your test environment manually in order to keep your test environment up to date. - Install test environment: -```cd protected/humhub/tests/codeception/bin``` - -```php yii migrate/up --includeModuleMigrations=1 --interactive=0``` - -```php yii installer/auto``` +`php yii installer/auto` - Set `HUMHUB_PATH` system variable The `HUMHUB_PATH` is used by your test environment to determine the humhub root path. This is only required for non core module tests and can also be set in your modules test configuration `/tests/config/test.php`: +``` return [ 'humhub_root' => '/path/to/my/humhub/root', ]; - +``` ## Test configuration @@ -74,11 +82,11 @@ The following configuration files can be used to overwrite the test configuratio The configurations for a suite will be merged in the following order: - - @humhub/protected/humhub/tests/config/functional.php - - @myModule/tests/config/common.php - - @myModule/tests/config/functional.php - - @myModule/tests/config/env/myenv/common.php (if exists) - - @myModule/tests/config/env/myenv/functional.php (if exists) + - `@humhub/protected/humhub/tests/config/functional.php` + - `@myModule/tests/config/common.php` + - `@myModule/tests/config/functional.php` + - `@myModule/tests/config/env/myenv/common.php (if exists)` + - `@myModule/tests/config/env/myenv/functional.php (if exists)` ### Environments @@ -106,17 +114,17 @@ humhub_root settings you want to use for this test run. ### Run all core tests: -´´´ +``` cd protected/humhub/tests/ codecept run -´´´ +``` ### Run core module test -´´´ +``` cd myModule/tests codecept run unit -´´´ +``` ### Run non core module tests @@ -147,18 +155,20 @@ return [ ### Run single test -``` -codecept run codeception/acceptance/TestCest:testFunction -``` +`codecept run codeception/acceptance/TestCest:testFunction` ### Run acceptance tests +Phantom.js and Selenium are needed as servers to run acceptance tests on your system. You can simply run codeception but selenium or phantomjs must be running. + +Hint: If your already installed webserver is listening on port 8080 you do not need to start the test server, because Humhub tests are run on port 8080. However if your `DocumentRoot` directory is not configured to directly open humhub via `localhost` you have to do some adjustments in the `codeception.yml` file of our `test` folder (`test-entry-url`) and the `acceptance.suite.yml` file. Alternatively start the test server as described below (in humhub root directory). + #### with phantomjs - Run phantomjs server (is installed with composer update) -```cd protected/vendor/bin``` +`cd protected/vendor/bin` -```phantomjs --webdriver=44444``` +`phantomjs --webdriver=44444` #### with chrome driver (selenium) @@ -172,11 +182,10 @@ java -Dwebdriver.chrome.driver=chromedriver.exe -jar selenium-server-standalone- Start test server: -```cd /myhumHubInstallation``` +`cd /myhumHubInstallation` -```php -S localhost:8080``` +`php -S localhost:8080` run with chrome environment: -```codecept run acceptance --env chrome``` - +`codecept run acceptance --env chrome` diff --git a/protected/humhub/docs/guide/theme/assets.md b/protected/humhub/docs/guide/theme/assets.md index dbeaa42dbb..86d6863ede 100644 --- a/protected/humhub/docs/guide/theme/assets.md +++ b/protected/humhub/docs/guide/theme/assets.md @@ -1,47 +1,8 @@ Custom Assets ============= -Structure ---------- - -Put your custom assets (e.g. images, fonts or javascripts) directly in the theme base directory. - -Example: - -- /themes/mytheme/img (Images) -- /themes/mytheme/js (Javascript files) -- /themes/mytheme/css (CSS Stylesheets) -- /themes/mytheme/font (Fonts) +TBD - -Usage ------- - -You can access the assets using the [[humhub/components/theme]] component. - -Example: - -``` -My logo -``` - - -Javascript and Stylesheets ---------------------------- - -In order to load additional **CSS** or **JavaScript** files in your theme, add them to `/themes/mytheme/views/layouts/head.php` - -e.g. - -``` - -``` - -Overwrite default images ------------------------- - -You can also overwrite default images (stored in /static/img/) by placing a custom image with the same file name in your theme image directory. - -If you want to replace the default user image as example, you need to create a file called **default_user.jpg** in your `/theme/mytheme/img` directory. +In order to load additional **CSS** or **JavaScript** files in your theme, add them to `/themes/mytheme/views/layouts/head.php` diff --git a/protected/humhub/docs/guide/theme/css.md b/protected/humhub/docs/guide/theme/css.md index f53c237f3e..62ad6df1e6 100644 --- a/protected/humhub/docs/guide/theme/css.md +++ b/protected/humhub/docs/guide/theme/css.md @@ -27,7 +27,7 @@ If you are using the command line tool [lessc](http://lesscss.org/), you can bui lessc -x themes/Example/less/build.less themes/Example/css/theme.css ``` -or respectively by using [grunt](../developer/core-build.md): +or respectively by using [grunt](../developer/build.md): ``` grunt build-theme --name=Example diff --git a/protected/humhub/docs/guide/theme/migrate-1.3.md b/protected/humhub/docs/guide/theme/migrate-1.3.md new file mode 100644 index 0000000000..aeb86f6071 --- /dev/null +++ b/protected/humhub/docs/guide/theme/migrate-1.3.md @@ -0,0 +1,5 @@ +# Theme Migration to HumHub 1.3 + +## Space & Profile Layouts + +The sidebars are now moved into own files `_sidebar.php` view files. diff --git a/protected/humhub/docs/guide/theme/migrate.md b/protected/humhub/docs/guide/theme/migrate.md index c70fe9aaee..1202081bea 100644 --- a/protected/humhub/docs/guide/theme/migrate.md +++ b/protected/humhub/docs/guide/theme/migrate.md @@ -11,7 +11,7 @@ All changes or additions will be automatically applied to your theme. ## View Files -As mentioned in the [View Files](view.md) section, you may need to manually adjust overwritten view files if there are any changes made in the new **HumHub** version. +As mentioned in the [View Files](views.md) section, you may need to manually adjust overwritten view files if there are any changes made in the new **HumHub** version. ### Identifing changes using Git command diff --git a/protected/humhub/docs/guide/theme/tutorial.md b/protected/humhub/docs/guide/theme/tutorial.md index 979e1443af..601ebc9878 100644 --- a/protected/humhub/docs/guide/theme/tutorial.md +++ b/protected/humhub/docs/guide/theme/tutorial.md @@ -1,10 +1,8 @@ Tutorial ======== - Foreword -------- - This quick tutorial shows you all necessary steps to create a custom **HumHub** theme. - Step 1: Create a theme folder ([Theme Folder Structure](structure.md)) @@ -14,23 +12,21 @@ This quick tutorial shows you all necessary steps to create a custom **HumHub** Step 1: Create an own theme folder --------------------------------- - - Go to the HumHub installation directory - Switch to the directory `themes` - Copy the folder `HumHub` and rename it to `Example` - Enable the new `Example` theme under `Administration -> Settings -> Appearance` - Step 2: Adjust colors and build CSS ----------------------------------- ### Installing prerequisites -** Install NodeJS ** +**Install NodeJS** See: https://nodejs.org/en/download/ -** Install LESS ** +**Install LESS** Open the command console and execute: @@ -39,7 +35,6 @@ npm -g install less ``` ### Modify theme colors - Add modifed color variables to the file `/themes/Example/less/variables.less`. ``` @@ -52,7 +47,6 @@ Add modifed color variables to the file `/themes/Example/less/variables.less`. ``` ### Compile LESS file - Open the command console and change to the themes `less` directory ``` @@ -66,25 +60,21 @@ lessc build.less ../css/theme.css ``` ### Test the result - - Flush your browsers cache - Flush the HumHub cache (if enabled): `Administration -> Settings -> Advanced -> Cache -> Flush` - Fully reload the page ![Example](images/color-example.png) - Step 3: Modify login template ------------------------------ In this step we're adding some text to the login template. ### Create a themed view file - Copy the view file `humhub/modules/user/views/auth/login.php` to `Example/views/user/auth/login.php` ### Modify the view file - Add some additional text below the application name. ```php @@ -100,11 +90,9 @@ Add some additional text below the application name. ``` Text Example: - ![Example Text](images/modify-template.png) ### Result - ![Result](images/modify-template-result.png) diff --git a/protected/humhub/libs/ActionColumn.php b/protected/humhub/libs/ActionColumn.php new file mode 100644 index 0000000000..c0a980a74c --- /dev/null +++ b/protected/humhub/libs/ActionColumn.php @@ -0,0 +1,106 @@ +options['style'] = 'width:56px'; + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + + $actions = $this->getActions($model, $key, $index); + + if (empty($actions)) { + return ''; + } + + $html = Html::beginTag('div', ['class' => 'btn-group dropdown-navigation']); + $html .= Html::button(' ', ['class' => 'btn btn-default dropdown-toggle', 'data-toggle' => 'dropdown']); + $html .= Html::beginTag('ul', ['class' => 'dropdown-menu pull-right']); + foreach ($actions as $title => $url) { + if ($url === '---') { + $html .= '
  • '; + } else { + $linkOptions = null; + if (isset($url['linkOptions'])) { + $linkOptions = $url['linkOptions']; + unset($url['linkOptions']); + } + + $html .= Html::beginTag('li'); + $html .= Html::a($title, $this->handleUrl($url, $model), $linkOptions); + $html .= Html::endTag('li'); + } + } + $html .= Html::endTag('ul'); + $html .= Html::endTag('div'); + + + return $html; + } + + protected function getActions($model, $key, $index) + { + if ($this->actions === null) { + return []; + } elseif (is_callable($this->actions)) { + return call_user_func($this->actions, $model, $key, $index, $this); + } + + return $this->actions; + } + + /** + * Builds the URL for a given Action + * + * @param array $url + * @param \yii\base\Model $model + * @return string the url + */ + protected function handleUrl($url, $model) + { + if (!isset($url[$this->modelIdAttribute])) { + $url[$this->modelIdAttribute] = $model->getAttribute($this->modelIdAttribute); + } + + return \yii\helpers\Url::to($url); + } + +} diff --git a/protected/humhub/libs/DynamicConfig.php b/protected/humhub/libs/DynamicConfig.php index ec3e8e40e4..bac0ff1f8c 100644 --- a/protected/humhub/libs/DynamicConfig.php +++ b/protected/humhub/libs/DynamicConfig.php @@ -9,6 +9,7 @@ namespace humhub\libs; use Yii; +use yii\base\Object; use yii\helpers\ArrayHelper; /** @@ -16,7 +17,7 @@ use yii\helpers\ArrayHelper; * * @author luke */ -class DynamicConfig extends \yii\base\Object +class DynamicConfig extends Object { /** @@ -26,7 +27,7 @@ class DynamicConfig extends \yii\base\Object */ public static function merge($new) { - $config = \yii\helpers\ArrayHelper::merge(self::load(), $new); + $config = ArrayHelper::merge(self::load(), $new); self::save($config); } @@ -49,7 +50,7 @@ class DynamicConfig extends \yii\base\Object $config = eval($configContent); if (!is_array($config)) - return array(); + return []; return $config; } @@ -61,15 +62,14 @@ class DynamicConfig extends \yii\base\Object */ public static function save($config) { - $content = "<" . "?php return "; + $content = '<' . '?php return '; $content .= var_export($config, true); - $content .= "; ?" . ">"; + $content .= '; ?' . '>'; $configFile = self::getConfigFilePath(); file_put_contents($configFile, $content); if (function_exists('opcache_invalidate')) { - opcache_reset(); opcache_invalidate($configFile); } @@ -91,14 +91,14 @@ class DynamicConfig extends \yii\base\Object // Add Default language $defaultLanguage = Yii::$app->settings->get('defaultLanguage'); - if ($defaultLanguage !== null && $defaultLanguage != "") { + if ($defaultLanguage !== null && $defaultLanguage != '') { $config['language'] = Yii::$app->settings->get('defaultLanguage'); } else { $config['language'] = Yii::$app->language; } $timeZone = Yii::$app->settings->get('timeZone'); - if ($timeZone != "") { + if ($timeZone != '') { $config['timeZone'] = $timeZone; $config['components']['formatter']['defaultTimeZone'] = $timeZone; $config['components']['formatterApp']['defaultTimeZone'] = $timeZone; @@ -118,17 +118,22 @@ class DynamicConfig extends \yii\base\Object 'keyPrefix' => Yii::$app->id, 'useApcu' => (function_exists('apcu_add')) ]; + } elseif ($cacheClass === \yii\redis\Cache::class) { + $config['components']['cache'] = [ + 'class' => \yii\redis\Cache::class, + 'keyPrefix' => Yii::$app->id, + ]; } // Add User settings - $config['components']['user'] = array(); + $config['components']['user'] = []; if (Yii::$app->getModule('user')->settings->get('auth.defaultUserIdleTimeoutSec')) { $config['components']['user']['authTimeout'] = Yii::$app->getModule('user')->settings->get('auth.defaultUserIdleTimeoutSec'); } // Install Mail Component $mail = []; - $mail['transport'] = array(); + $mail['transport'] = []; if (Yii::$app->settings->get('mailer.transportType') == 'smtp') { $mail['transport']['class'] = 'Swift_SmtpTransport'; diff --git a/protected/humhub/libs/Html.php b/protected/humhub/libs/Html.php index d998b34422..68364d1ef5 100644 --- a/protected/humhub/libs/Html.php +++ b/protected/humhub/libs/Html.php @@ -11,6 +11,8 @@ namespace humhub\libs; use Yii; use yii\base\InvalidParamException; use humhub\modules\content\components\ContentContainerActiveRecord; +use humhub\modules\user\models\User; +use humhub\modules\space\models\Space; /** * HTML Helpers @@ -94,9 +96,12 @@ class Html extends \yii\bootstrap\Html */ public static function containerLink(ContentContainerActiveRecord $container, $options = []) { - if ($container instanceof \humhub\modules\space\models\Space) { + if ($container instanceof Space) { return static::a(static::encode($container->name), $container->getUrl(), $options); - } elseif ($container instanceof \humhub\modules\user\models\User) { + } elseif ($container instanceof User) { + if ($container->status == User::STATUS_SOFT_DELETED) { + return static::beginTag('strike') . static::encode($container->displayName) . static::endTag('strike'); + } return static::a(static::encode($container->displayName), $container->getUrl(), $options); } else { throw new InvalidParamException('Content container type not supported!'); diff --git a/protected/humhub/migrations/m171015_155102_contentcontainer_module.php b/protected/humhub/migrations/m171015_155102_contentcontainer_module.php new file mode 100644 index 0000000000..9f2b1035c4 --- /dev/null +++ b/protected/humhub/migrations/m171015_155102_contentcontainer_module.php @@ -0,0 +1,62 @@ +createTable('contentcontainer_module', [ + 'contentcontainer_id' => $this->integer()->notNull(), + 'module_id' => $this->char(100), + 'module_state' => $this->smallInteger(), + ]); + $this->addPrimaryKey('pk_contentcontainer_module', 'contentcontainer_module', ['contentcontainer_id', 'module_id']); + $this->addForeignKey('fk_contentcontainer', 'contentcontainer_module', 'contentcontainer_id', 'contentcontainer', 'id', 'CASCADE', 'CASCADE'); + + $sqlInsert = 'INSERT INTO contentcontainer_module (contentcontainer_id, module_id, module_state) '; + $this->db->createCommand($sqlInsert . 'SELECT space.contentcontainer_id, module_id, state FROM space_module LEFT JOIN space ON space_module.space_id=space.id WHERE space.id IS NOT NULL')->execute(); + $this->db->createCommand($sqlInsert . 'SELECT user.contentcontainer_id, module_id, state FROM user_module LEFT JOIN user ON user_module.user_id=user.id WHERE user.id IS NOT NULL')->execute(); + + $rows = (new \yii\db\Query())->select("*")->from('space_module')->where('space_id IS NULL OR space_id=0')->all(); + foreach ($rows as $row) { + $reflect = new ReflectionClass(humhub\modules\space\models\Space::class); + $module = Yii::$app->getModule($row['module_id']); + $module->settings->set('moduleManager.defaultState.' . $reflect->getShortName(), $row['state']); + } + + + $rows = (new \yii\db\Query())->select("*")->from('user_module')->where('user_id IS NULL OR user_id=0')->all(); + foreach ($rows as $row) { + $reflect = new ReflectionClass(\humhub\modules\user\models\User::class); + $module = Yii::$app->getModule($row['module_id']); + $module->settings->set('moduleManager.defaultState.' . $reflect->getShortName(), $row['state']); + } + + $this->dropTable('user_module'); + $this->dropTable('space_module'); + } + + public function safeDown() + { + echo "m171015_155102_contentcontainer_module cannot be reverted.\n"; + + return false; + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m171015_155102_contentcontainer_module cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/protected/humhub/modules/activity/jobs/SendMailSummary.php b/protected/humhub/modules/activity/jobs/SendMailSummary.php index 91034ccc07..f640a75fc1 100644 --- a/protected/humhub/modules/activity/jobs/SendMailSummary.php +++ b/protected/humhub/modules/activity/jobs/SendMailSummary.php @@ -9,7 +9,7 @@ namespace humhub\modules\activity\jobs; use Yii; -use humhub\components\queue\ActiveJob; +use humhub\modules\queue\ActiveJob; use humhub\modules\activity\components\MailSummaryProcessor; use humhub\modules\activity\components\MailSummary; diff --git a/protected/humhub/modules/admin/Module.php b/protected/humhub/modules/admin/Module.php index 2081b6fa66..09ab52cf5f 100644 --- a/protected/humhub/modules/admin/Module.php +++ b/protected/humhub/modules/admin/Module.php @@ -48,6 +48,11 @@ class Module extends \humhub\components\Module */ public $dailyCheckForNewVersion = true; + /** + * @var boolean allow admins to impersonate other users + */ + public $allowUserImpersonate = true; + /** * @inheritdoc */ diff --git a/protected/humhub/modules/admin/controllers/AuthenticationController.php b/protected/humhub/modules/admin/controllers/AuthenticationController.php index 5288866907..99fa8d8050 100644 --- a/protected/humhub/modules/admin/controllers/AuthenticationController.php +++ b/protected/humhub/modules/admin/controllers/AuthenticationController.php @@ -1,15 +1,23 @@ subLayout = '@admin/views/layouts/user'; - - return parent::init(); + + return parent::init(); } /** @@ -44,38 +52,48 @@ class AuthenticationController extends Controller public function getAccessRules() { return [ - ['permissions' => \humhub\modules\admin\permissions\ManageSettings::className()] + ['permissions' => ManageSettings::className()] ]; } /** * Returns a List of Users + * @return string */ public function actionIndex() { - $form = new \humhub\modules\admin\models\forms\AuthenticationSettingsForm; + $form = new AuthenticationSettingsForm; if ($form->load(Yii::$app->request->post()) && $form->validate() && $form->save()) { $this->view->saved(); } // Build Group Dropdown - $groups = []; - $groups[''] = Yii::t('AdminModule.controllers_SettingController', 'None - shows dropdown in user registration.'); - foreach (\humhub\modules\user\models\Group::find()->all() as $group) { + $groups = [ + '' => Yii::t( + 'AdminModule.controllers_SettingController', + 'None - shows dropdown in user registration.' + ) + ]; + + foreach (Group::find()->all() as $group) { if (!$group->is_admin_group) { $groups[$group->id] = $group->name; } } return $this->render('authentication', [ - 'model' => $form, - 'groups' => $groups - ]); + 'model' => $form, + 'groups' => $groups + ]); } + /** + * Configure Ldap authentication + * @return string + */ public function actionAuthenticationLdap() { - $form = new \humhub\modules\admin\models\forms\AuthenticationLdapSettingsForm; + $form = new AuthenticationLdapSettingsForm; if ($form->load(Yii::$app->request->post()) && $form->validate() && $form->save()) { $this->view->saved(); return $this->redirect(['/admin/authentication/authentication-ldap']); @@ -88,16 +106,16 @@ class AuthenticationController extends Controller if (Yii::$app->getModule('user')->settings->get('auth.ldap.enabled')) { $enabled = true; try { - $ldapAuthClient = new \humhub\modules\user\authclient\ZendLdapClient(); + $ldapAuthClient = new ZendLdapClient(); $ldap = $ldapAuthClient->getLdap(); $userCount = $ldap->count( Yii::$app->getModule('user')->settings->get('auth.ldap.userFilter'), Yii::$app->getModule('user')->settings->get('auth.ldap.baseDn'), - \Zend\Ldap\Ldap::SEARCH_SCOPE_SUB + Ldap::SEARCH_SCOPE_SUB ); - } catch (\Zend\Ldap\Exception\LdapException $ex) { + } catch (LdapException $ex) { $errorMessage = $ex->getMessage(); - } catch (\Exception $ex) { + } catch (Exception $ex) { $errorMessage = $ex->getMessage(); } } diff --git a/protected/humhub/modules/admin/controllers/ModuleController.php b/protected/humhub/modules/admin/controllers/ModuleController.php index 4de46af784..e45133f82f 100644 --- a/protected/humhub/modules/admin/controllers/ModuleController.php +++ b/protected/humhub/modules/admin/controllers/ModuleController.php @@ -15,6 +15,8 @@ use humhub\modules\admin\libs\OnlineModuleManager; use humhub\modules\content\components\ContentContainerModule; use humhub\modules\user\models\User; use humhub\modules\space\models\Space; +use humhub\modules\admin\models\forms\ModuleSetAsDefaultForm; +use humhub\modules\content\components\ContentContainerModuleManager; /** * Module Controller controls all third party modules in a humhub installation. @@ -300,45 +302,13 @@ class ModuleController extends Controller throw new HttpException(500, 'Invalid module type!'); } - $model = new \humhub\modules\admin\models\forms\ModuleSetAsDefaultForm(); - - $spaceDefaultModule = null; - if ($module->hasContentContainerType(Space::className())) { - $spaceDefaultModule = \humhub\modules\space\models\Module::find()->where(['module_id' => $moduleId])->andWhere(['IS', 'space_id', new \yii\db\Expression('NULL')])->one(); - if ($spaceDefaultModule === null) { - $spaceDefaultModule = new \humhub\modules\space\models\Module(); - $spaceDefaultModule->module_id = $moduleId; - $spaceDefaultModule->state = \humhub\modules\space\models\Module::STATE_DISABLED; - } - $model->spaceDefaultState = $spaceDefaultModule->state; - } - - $userDefaultModule = null; - if ($module->hasContentContainerType(User::className())) { - $userDefaultModule = \humhub\modules\user\models\Module::find()->where(['module_id' => $moduleId])->andWhere(['IS', 'user_id', new \yii\db\Expression('NULL')])->one(); - if ($userDefaultModule === null) { - $userDefaultModule = new \humhub\modules\user\models\Module(); - $userDefaultModule->module_id = $moduleId; - $userDefaultModule->state = \humhub\modules\user\models\Module::STATE_DISABLED; - } - $model->userDefaultState = $userDefaultModule->state; - } + $model = new ModuleSetAsDefaultForm(); + $model->spaceDefaultState = ContentContainerModuleManager::getDefaultState(Space::class, $moduleId); + $model->userDefaultState = ContentContainerModuleManager::getDefaultState(User::class, $moduleId); if ($model->load(Yii::$app->request->post()) && $model->validate()) { - if ($module->hasContentContainerType(Space::className())) { - $spaceDefaultModule->state = $model->spaceDefaultState; - if (!$spaceDefaultModule->save()) { - throw new HttpException('Could not save: ' . print_r($spaceDefaultModule->getErrors(), 1)); - } - } - - if ($module->hasContentContainerType(User::className())) { - $userDefaultModule->state = $model->userDefaultState; - if (!$userDefaultModule->save()) { - throw new HttpException('Could not save: ' . print_r($userDefaultModule->getErrors(), 1)); - } - } - + ContentContainerModuleManager::setDefaultState(User::class, $moduleId, $model->userDefaultState); + ContentContainerModuleManager::setDefaultState(Space::class, $moduleId, $model->spaceDefaultState); return $this->renderModalClose(); } diff --git a/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php b/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php index 09f527a000..c483790c36 100644 --- a/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php +++ b/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php @@ -6,34 +6,42 @@ * */ -/** - * Created by PhpStorm. - * User: buddha - * Date: 31.07.2017 - * Time: 13:25 - */ - namespace humhub\modules\admin\controllers; - -use DateTime; -use humhub\components\ActiveRecord; use humhub\modules\admin\components\Controller; use humhub\modules\admin\models\PendingRegistrationSearch; use humhub\modules\admin\permissions\ManageGroups; use humhub\modules\admin\permissions\ManageUsers; +use humhub\modules\user\models\Invite; use PHPExcel; use PHPExcel_Cell; +use PHPExcel_Exception; use PHPExcel_IOFactory; use PHPExcel_Shared_Date; use PHPExcel_Style_NumberFormat; -use PHPExcel_Writer_Excel2007; -use function PHPSTORM_META\type; +use PHPExcel_Worksheet; use Yii; -use yii\helpers\ArrayHelper; +use yii\helpers\Url; +use yii\web\HttpException; class PendingRegistrationsController extends Controller { + + const EXPORT_CSV = 'csv'; + const EXPORT_XLSX = 'xsls'; + const EXPORT_PREFIX = 'pur_export'; + + const EXPORT_COLUMNS = [ + 'email', + 'originator.username', + 'language', + 'source', + 'created_at', + ]; + + /** + * @inheritdoc + */ public function init() { $this->subLayout = '@admin/views/layouts/user'; @@ -56,24 +64,117 @@ class PendingRegistrationsController extends Controller ]; } + + /** + * Render PendingRegistrations + * + * @param bool $export + * @param null $format + * @return string + */ public function actionIndex($export = false, $format = null) { $searchModel = new PendingRegistrationSearch(); $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - if($export) { - return $this->createCVS($dataProvider, $searchModel, $format); - } + $urlExportCsv = Url::to([ + 'export', + 'format' => self::EXPORT_CSV, + 'PendingRegistrationSearch' => Yii::$app->request->get('PendingRegistrationSearch') + ]); + $urlExportXlsx = Url::to([ + 'export', + 'format' => self::EXPORT_XLSX, + 'PendingRegistrationSearch' => Yii::$app->request->get('PendingRegistrationSearch') + ]); return $this->render('index', [ 'dataProvider' => $dataProvider, 'searchModel' => $searchModel, - 'types' => $this->getTypeMapping() + 'urlExportCsv' => $urlExportCsv, + 'urlExportXlsx' => $urlExportXlsx, + 'types' => $this->typeMapping(), ]); } - public function getTypeMapping() + /** + * Export PendingRegistrations + * + * @param string $format + * @throws PHPExcel_Exception + */ + public function actionExport($format) + { + $searchModel = new PendingRegistrationSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + /** @var PHPExcel $file */ + $file = $this->createCsvFile(); + + /** @var PHPExcel_Worksheet $worksheet */ + $worksheet = $file->getActiveSheet(); + + // Row counter + $rowCount = 1; + + // Build Header + $this->buildCsvHeaderRow($worksheet, $rowCount, $searchModel); + + // Set format for Date fields + $formatDate = $format === self::EXPORT_CSV + ? Yii::$app->formatter->getDateTimePattern() + : PHPExcel_Style_NumberFormat::FORMAT_DATE_DATETIME; + + // Build Rows + foreach ($dataProvider->query->all() as $record) { + $rowCount++; + $this->buildCsvRow($rowCount, $record, $worksheet, $formatDate); + } + + if ($format === self::EXPORT_CSV) { + $this->exportAsCsv($file); + } else { + $this->exportAsXlsx($file); + } + } + + /** + * Resend a invite + * + * @param integer $id + * @return string + * @throws HttpException + */ + public function actionResend($id) + { + $invite = Invite::findOne(['id' => $id]); + if ($invite === null) { + throw new HttpException(404, Yii::t( + 'AdminModule.controllers_PendingRegistrationsController', + 'Invite not found!' + )); + } + + if (Yii::$app->request->isPost) { + $invite->sendInviteMail(); + $invite->save(); + $invite->refresh(); + $this->view->success(Yii::t( + 'AdminModule.controllers_PendingRegistrationsController', + 'Resend invitation email' + )); + } + + return $this->render('resend', ['model' => $invite]); + } + + /** + * Return type mapping + * + * @return array + */ + private function typeMapping() { return [ PendingRegistrationSearch::SOURCE_INVITE => Yii::t('AdminModule.base', 'Invite'), @@ -81,78 +182,105 @@ class PendingRegistrationsController extends Controller ]; } - public function createCVS($dataProvider, ActiveRecord $model, $format = null) + /** + * Export the file as Csv + * + * @param PHPExcel $file + * @throws \PHPExcel_Reader_Exception + * @throws \PHPExcel_Writer_Exception + */ + private function exportAsCsv($file) { - $columns = [ - ['email'], - ['originator.username'], - ['language'], - ['source'], - ['created_at', 'type' => 'datetime'], - ]; - - $file = new PHPExcel(); - $file->getProperties()->setCreator('HumHub'); - $file->getProperties()->setTitle(Yii::t('AdminModule.base', 'Pending user registrations')); - $file->getProperties()->setSubject(Yii::t('AdminModule.base', 'Pending user registrations')); - $file->getProperties()->setDescription(Yii::t('AdminModule.base', 'Pending user registrations')); - - $file->setActiveSheetIndex(0); - $worksheet = $file->getActiveSheet(); - - $worksheet->setTitle(Yii::t('AdminModule.base', 'Pending user registrations')); - - // Creat header - $row = 1; - $lastColumn = count($columns); - for ($column = 0; $column != $lastColumn; $column++) { - $columnKey = PHPExcel_Cell::stringFromColumnIndex($column); - $worksheet->getColumnDimension($columnKey)->setWidth(30); - $worksheet->setCellValueByColumnAndRow($column, $row, $model->getAttributeLabel($columns[$column][0])); - } - - $row++; - - // Fill content header - foreach($dataProvider->query->all() as $record) { - for ($column = 0; $column != $lastColumn; $column++) { - $attribute = $columns[$column][0]; - $value = ArrayHelper::getValue($record,$attribute); - - if(isset($columns[$column]['type']) && $columns[$column]['type'] === 'datetime') { - $value = PHPExcel_Shared_Date::PHPToExcel(new DateTime($value)); - if($format === 'CSV') { - $worksheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(Yii::$app->formatter->getDateTimePattern()); - } else { - $worksheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(PHPExcel_Style_NumberFormat::FORMAT_DATE_DATETIME); - } - } - - if($attribute === 'source') { - $types = $this->getTypeMapping(); - $value = isset($types[$value]) ? $types[$value] : $value; - } - - $worksheet->setCellValueByColumnAndRow($column, $row, $value); - } - $row++; - } - - $filePrefix = 'pur_export_'.time(); - if($format === 'CSV') { - $writer = PHPExcel_IOFactory::createWriter($file, 'CSV'); - $writer->setDelimiter(';'); - - header('Content-Type: application/csv'); - header('Content-Disposition: attachment;filename="'.$filePrefix.'.csv"'); - header('Cache-Control: max-age=0'); - } else { - $writer = PHPExcel_IOFactory::createWriter($file, 'Excel2007'); - header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - header('Content-Disposition: attachment; filename="'.$filePrefix.'.xlsx"'); - header('Cache-Control: max-age=0'); - } + header('Content-Type: application/csv'); + header('Content-Disposition: attachment;filename="' . self::EXPORT_PREFIX . '_' . time() . '.csv"'); + header('Cache-Control: max-age=0'); + /** @var \PHPExcel_Writer_CSV $writer */ + $writer = PHPExcel_IOFactory::createWriter($file, 'CSV'); + $writer->setDelimiter(';'); $writer->save('php://output'); } -} \ No newline at end of file + + /** + * Export the file as Xlsx + * + * @param PHPExcel $file + * @throws \PHPExcel_Reader_Exception + * @throws \PHPExcel_Writer_Exception + */ + private function exportAsXlsx($file) + { + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Content-Disposition: attachment;filename="' . self::EXPORT_PREFIX . '_' . time() . '.xlsx"'); + header('Cache-Control: max-age=0'); + + /** @var \PHPExcel_Writer_Excel2007 $writer */ + $writer = PHPExcel_IOFactory::createWriter($file, 'Excel2007'); + $writer->save('php://output'); + } + + /** + * Build a row for csv document + * + * @param integer $row + * @param PendingRegistrationSearch $record + * @param PHPExcel_Worksheet $worksheet + * @param string $formatDate + */ + private function buildCsvRow($row, $record, $worksheet, $formatDate) + { + for ($i = 0; $i < count(self::EXPORT_COLUMNS); $i++) { + $name = self::EXPORT_COLUMNS[$i]; + $value = $record->{$name}; + + if ($name === 'source') { + $typeMapping = $this->typeMapping(); + $value = isset($typeMapping[$value]) ? $typeMapping[$value] : $value; + } + + if ($name === 'created_at') { + $worksheet->getStyleByColumnAndRow($i, $row)->getNumberFormat()->setFormatCode($formatDate); + $value = PHPExcel_Shared_Date::PHPToExcel(new \DateTime($value)); + } + + $worksheet->setCellValueByColumnAndRow($i, $row, $value); + } + } + + /** + * Build header row for csv document + * + * @param PHPExcel_Worksheet $worksheet + * @param integer $row + * @param PendingRegistrationSearch $searchModel + */ + private function buildCsvHeaderRow($worksheet, $row, $searchModel) + { + for ($i = 0; $i < count(self::EXPORT_COLUMNS); $i++) { + $worksheet->getColumnDimension(PHPExcel_Cell::stringFromColumnIndex($i))->setWidth(30); + $worksheet->setCellValueByColumnAndRow($i, $row, $searchModel->getAttributeLabel(self::EXPORT_COLUMNS[$i])); + } + } + + /** + * Return new PHPExcel file + * + * @return PHPExcel + */ + private function createCsvFile() + { + $title = Yii::t( + 'AdminModule.base', + 'Pending user registrations' + ); + + /** @var PHPExcel $file */ + $file = new PHPExcel(); + $file->getProperties() + ->setCreator('HumHub') + ->setTitle($title) + ->setSubject($title) + ->setDescription($title); + return $file; + } +} diff --git a/protected/humhub/modules/admin/controllers/SpaceController.php b/protected/humhub/modules/admin/controllers/SpaceController.php index 46ebb387f0..dba34e5ed9 100644 --- a/protected/humhub/modules/admin/controllers/SpaceController.php +++ b/protected/humhub/modules/admin/controllers/SpaceController.php @@ -49,10 +49,7 @@ class SpaceController extends Controller public function getAccessRules() { return [ - ['permissions' => [ - ManageSpaces::className(), - ManageSettings::className() - ]], + ['permissions' => [ManageSpaces::className(), ManageSettings::className()]], ]; } @@ -61,21 +58,42 @@ class SpaceController extends Controller */ public function actionIndex() { - if (Yii::$app->user->can(new ManageSpaces())) { - $searchModel = new SpaceSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - 'searchModel' => $searchModel - ]); - } else if (Yii::$app->user->can(new ManageSettings())) { - return $this->redirect([ - 'settings' - ]); + if (!Yii::$app->user->can(new ManageSpaces())) { + return $this->redirect(['settings']); } - throw new HttpException(403); + $searchModel = new SpaceSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel + ]); + } + + /** + * Deep link into space + */ + public function actionOpen($id, $section) + { + $space = Space::findOne(['id' => $id]); + if ($space === null) { + throw new HttpException(404); + } + + if ($section == 'members') { + return $this->redirect($space->createUrl('/space/manage/member')); + } elseif ($section == 'owner') { + return $this->redirect($space->createUrl('/space/manage/member/change-owner')); + } elseif ($section == 'edit') { + return $this->redirect($space->createUrl('/space/manage')); + } elseif ($section == 'modules') { + return $this->redirect($space->createUrl('/space/manage/module')); + } elseif ($section == 'delete') { + return $this->redirect($space->createUrl('/space/manage/default/delete')); + } else { + return $this->redirect($space->getUrl()); + } } /** @@ -109,11 +127,11 @@ class SpaceController extends Controller Content::VISIBILITY_PUBLIC => Yii::t('SpaceModule.base', 'Public')]; return $this->render('settings', [ - 'model' => $form, - 'joinPolicyOptions' => $joinPolicyOptions, - 'visibilityOptions' => $visibilityOptions, - 'contentVisibilityOptions' => $contentVisibilityOptions - ] + 'model' => $form, + 'joinPolicyOptions' => $joinPolicyOptions, + 'visibilityOptions' => $visibilityOptions, + 'contentVisibilityOptions' => $contentVisibilityOptions + ] ); } diff --git a/protected/humhub/modules/admin/controllers/UserController.php b/protected/humhub/modules/admin/controllers/UserController.php index 07078127fc..6fc3162668 100644 --- a/protected/humhub/modules/admin/controllers/UserController.php +++ b/protected/humhub/modules/admin/controllers/UserController.php @@ -12,14 +12,16 @@ use Yii; use yii\helpers\Url; use yii\web\HttpException; use humhub\compat\HForm; +use humhub\modules\user\models\User; +use humhub\modules\user\models\Invite; use humhub\modules\user\models\forms\Registration; use humhub\modules\admin\components\Controller; -use humhub\modules\user\models\User; use humhub\modules\admin\models\forms\UserEditForm; use humhub\modules\admin\permissions\ManageUsers; use humhub\modules\admin\permissions\ManageGroups; use humhub\modules\admin\permissions\ManageSettings; -use humhub\modules\space\models\Membership; +use humhub\modules\admin\models\forms\UserDeleteForm; +use humhub\modules\admin\models\UserSearch; /** * User management @@ -34,12 +36,15 @@ class UserController extends Controller */ public $adminOnly = false; + /** + * @inheritdoc + */ public function init() { + parent::init(); + $this->appendPageTitle(Yii::t('AdminModule.base', 'Users')); $this->subLayout = '@admin/views/layouts/user'; - - return parent::init(); } /** @@ -48,36 +53,37 @@ class UserController extends Controller public function getAccessRules() { return [ - [ - 'permissions' => [ - ManageUsers::class, - ManageGroups::class, - ] - ], - [ - 'permissions' => [ManageSettings::class], - 'actions' => ['index'] - ] + ['permissions' => [ManageUsers::class, ManageGroups::class]], + ['permissions' => [ManageSettings::class], 'actions' => ['index']] ]; } + public function actionIndex() + { + if (Yii::$app->user->can([new ManageUsers(), new ManageGroups()])) { + return $this->redirect(['list']); + } else if (Yii::$app->user->can(ManageSettings::class)) { + return $this->redirect(['/admin/authentication']); + } else { + return $this->forbidden(); + } + } + /** * Returns a List of Users */ - public function actionIndex() + public function actionList() { - if (Yii::$app->user->can([new ManageUsers(), new ManageGroups()])) { - $searchModel = new \humhub\modules\admin\models\UserSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - return $this->render('index', [ - 'dataProvider' => $dataProvider, - 'searchModel' => $searchModel - ]); - } else if (Yii::$app->user->can(ManageSettings::class)) { - $this->redirect(['/admin/authentication']); - } else { - $this->forbidden(); - } + $searchModel = new UserSearch(); + $searchModel->status = User::STATUS_ENABLED; + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + $showPendingRegistrations = (Invite::find()->count() > 0 && Yii::$app->user->can([new ManageUsers(), new ManageGroups()])); + + return $this->render('list', [ + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, + 'showPendingRegistrations' => $showPendingRegistrations + ]); } /** @@ -148,12 +154,6 @@ class UserController extends Controller 'label' => Yii::t('AdminModule.controllers_UserController', 'Save'), 'class' => 'btn btn-primary', ], - 'become' => [ - 'type' => 'submit', - 'label' => Yii::t('AdminModule.controllers_UserController', 'Become this user'), - 'class' => 'btn btn-danger', - 'isVisible' => $this->canBecomeUser($user) - ], 'delete' => [ 'type' => 'submit', 'label' => Yii::t('AdminModule.controllers_UserController', 'Delete'), @@ -172,15 +172,8 @@ class UserController extends Controller } } - // This feature is used primary for testing, maybe remove this in future - if ($form->submitted('become') && $this->canBecomeUser($user)) { - - Yii::$app->user->switchIdentity($form->models['User']); - return $this->redirect(Url::home()); - } - if ($form->submitted('delete')) { - return $this->redirect(['/admin/user/delete', 'id' => $user->id]); + return $this->redirect(['delete', 'id' => $user->id]); } return $this->render('edit', [ @@ -189,11 +182,6 @@ class UserController extends Controller ]); } - public function canBecomeUser($user) - { - return Yii::$app->user->isAdmin() && $user->id != Yii::$app->user->getIdentity()->id; - } - public function actionAdd() { $registration = new Registration(); @@ -209,33 +197,109 @@ class UserController extends Controller /** * Deletes a user permanently */ - public function actionDelete() + public function actionDelete($id) { - $id = (int) Yii::$app->request->get('id'); - $doit = (int) Yii::$app->request->get('doit'); + $user = User::findOne(['id' => $id]); + if ($user == null) { + throw new HttpException(404, Yii::t('AdminModule.user', 'User not found!')); + } elseif (Yii::$app->user->id == $id) { + throw new HttpException(400, Yii::t('AdminModule.user', 'You cannot delete yourself!')); + } + + $model = new UserDeleteForm(['user' => $user]); + if ($model->load(Yii::$app->request->post()) && $model->performDelete()) { + $this->view->info(Yii::t('AdminModule.user', 'User deletion process queued.')); + return $this->redirect(['list']); + } + + return $this->render('delete', ['model' => $model]); + } + + /** + * Redirect to user profile + * + * @param int $id + * @return \yii\base\Response the response + * @throws HttpException + */ + public function actionViewProfile($id) + { + $user = User::findOne(['id' => $id]); + if ($user === null) { + throw new HttpException(404); + } + + return $this->redirect($user->getUrl()); + } + + public function actionEnable($id) + { + $this->forcePostRequest(); $user = User::findOne(['id' => $id]); - - if ($user == null) { - throw new HttpException(404, Yii::t('AdminModule.controllers_UserController', 'User not found!')); - } elseif (Yii::$app->user->id == $id) { - throw new HttpException(400, Yii::t('AdminModule.controllers_UserController', 'You cannot delete yourself!')); + if ($user === null) { + throw new HttpException(404); } - if ($doit == 2) { - $this->forcePostRequest(); + $user->status = User::STATUS_ENABLED; + $user->save(); - foreach (Membership::GetUserSpaces($user->id) as $space) { - if ($space->isSpaceOwner($user->id)) { - $space->addMember(Yii::$app->user->id); - $space->setSpaceOwner(Yii::$app->user->id); - } - } - $user->delete(); - return $this->redirect(['/admin/user']); + return $this->redirect(['list']); + } + + public function actionDisable($id) + { + $this->forcePostRequest(); + + $user = User::findOne(['id' => $id]); + if ($user === null) { + throw new HttpException(404); } - return $this->render('delete', ['model' => $user]); + $user->status = User::STATUS_DISABLED; + $user->save(); + + return $this->redirect(['list']); + } + + /** + * Redirect to user profile + * + * @param int $id + * @return \yii\base\Response the response + * @throws HttpException + */ + public function actionImpersonate($id) + { + $this->forcePostRequest(); + + $user = User::findOne(['id' => $id]); + if ($user === null) { + throw new HttpException(404); + } + + if (!static::canImpersonate($user)) { + throw new HttpException(403); + } + + Yii::$app->user->switchIdentity($user); + + return $this->goHome(); + } + + /** + * Determines if the current user can impersonate given user. + * + * @param User $user + * @return boolean can impersonate + */ + public static function canImpersonate($user) + { + if (!Yii::$app->getModule('admin')->allowUserImpersonate) { + return false; + } + + return Yii::$app->user->isAdmin() && $user->id != Yii::$app->user->getIdentity()->id; } } diff --git a/protected/humhub/modules/admin/grid/SpaceActionColumn.php b/protected/humhub/modules/admin/grid/SpaceActionColumn.php new file mode 100644 index 0000000000..b0143fabbc --- /dev/null +++ b/protected/humhub/modules/admin/grid/SpaceActionColumn.php @@ -0,0 +1,43 @@ + 'edit']; + $actions[] = '---'; + $actions[Yii::t('AdminModule.space', 'Manage members')] = ['open', 'section' => 'members']; + $actions[Yii::t('AdminModule.space', 'Change owner')] = ['open', 'section' => 'owner']; + $actions[Yii::t('AdminModule.space', 'Manage modules')] = ['open', 'section' => 'modules']; + $actions[Yii::t('base', 'Delete')] = ['open', 'section' => 'delete']; + $actions[] = '---'; + $actions[Yii::t('AdminModule.space', 'Open space')] = ['open']; + $this->actions = $actions; + + return parent::renderDataCellContent($model, $key, $index); + } + +} diff --git a/protected/humhub/modules/admin/grid/SpaceBaseColumn.php b/protected/humhub/modules/admin/grid/SpaceBaseColumn.php new file mode 100644 index 0000000000..5acdde8b47 --- /dev/null +++ b/protected/humhub/modules/admin/grid/SpaceBaseColumn.php @@ -0,0 +1,44 @@ +spaceAttribute === null) { + return $record; + } + + $attributeName = $this->spaceAttribute; + return $record->$attributeName; + } + +} diff --git a/protected/humhub/modules/admin/grid/SpaceImageColumn.php b/protected/humhub/modules/admin/grid/SpaceImageColumn.php new file mode 100644 index 0000000000..e0128823c7 --- /dev/null +++ b/protected/humhub/modules/admin/grid/SpaceImageColumn.php @@ -0,0 +1,40 @@ +options['style'] = 'width:38px'; + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + return SpaceImage::widget(['space' => $this->getSpace($model), 'width' => 34, 'link' => true]); + } + +} diff --git a/protected/humhub/modules/admin/grid/SpaceTitleColumn.php b/protected/humhub/modules/admin/grid/SpaceTitleColumn.php new file mode 100644 index 0000000000..033248caae --- /dev/null +++ b/protected/humhub/modules/admin/grid/SpaceTitleColumn.php @@ -0,0 +1,56 @@ +attribute === null) { + $this->attribute = 'name'; + } + + if ($this->label === null) { + $this->label = Yii::t('SpaceModule.base', 'Name'); + } + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + $space = $this->getSpace($model); + + $badge = ''; + if ($space->status == Space::STATUS_ARCHIVED) { + $badge = ' '.Yii::t('SpaceModule.base', 'Archived').''; + } + + return '
    ' . Html::encode($space->name) . $badge . '
    ' . + '' . Html::encode(Helpers::trimText($space->description, 100)) . '
    '; + } + +} diff --git a/protected/humhub/modules/admin/grid/UserActionColumn.php b/protected/humhub/modules/admin/grid/UserActionColumn.php new file mode 100644 index 0000000000..e4c59edbb8 --- /dev/null +++ b/protected/humhub/modules/admin/grid/UserActionColumn.php @@ -0,0 +1,55 @@ +status == User::STATUS_SOFT_DELETED) { + $actions[Yii::t('AdminModule.user', 'Permanently delete')] = ['delete']; + } else { + $actions[Yii::t('base', 'Edit')] = ['edit']; + $actions[] = '---'; + if ($model->status == User::STATUS_DISABLED) { + $actions[Yii::t('AdminModule.user', 'Enable')] = ['enable', 'linkOptions' => ['data-method' => 'post', 'data-confirm' => Yii::t('AdminModule.user', 'Are you really sure that you want to enable this user?')]]; + } elseif ($model->status == User::STATUS_ENABLED) { + $actions[Yii::t('AdminModule.user', 'Disable')] = ['disable', 'linkOptions' => ['data-method' => 'post', 'data-confirm' => Yii::t('AdminModule.user', 'Are you really sure that you want to disable this user?')]]; + } + $actions[Yii::t('base', 'Delete')] = ['delete']; + + if ($model->status == User::STATUS_ENABLED) { + $actions[] = '---'; + if (UserController::canImpersonate($model)) { + $actions[Yii::t('AdminModule.user', 'Impersonate')] = ['impersonate', 'linkOptions' => ['data-method' => 'post', 'data-confirm' => Yii::t('AdminModule.user', 'Are you really sure that you want to impersonate this user?')]]; + } + $actions[Yii::t('AdminModule.user', 'View profile')] = ['view-profile']; + } + } + $this->actions = $actions; + + return parent::renderDataCellContent($model, $key, $index); + } + +} diff --git a/protected/humhub/modules/admin/jobs/CheckForNewVersion.php b/protected/humhub/modules/admin/jobs/CheckForNewVersion.php index a251df8894..163bc2a573 100644 --- a/protected/humhub/modules/admin/jobs/CheckForNewVersion.php +++ b/protected/humhub/modules/admin/jobs/CheckForNewVersion.php @@ -9,7 +9,7 @@ namespace humhub\modules\admin\jobs; use Yii; -use humhub\components\queue\ActiveJob; +use humhub\modules\queue\ActiveJob; use humhub\modules\user\models\Group; use humhub\modules\admin\libs\HumHubAPI; use humhub\modules\admin\notifications\NewVersionAvailable; diff --git a/protected/humhub/modules/admin/jobs/CleanupLog.php b/protected/humhub/modules/admin/jobs/CleanupLog.php index 6f4cdb40fa..7db970b20f 100644 --- a/protected/humhub/modules/admin/jobs/CleanupLog.php +++ b/protected/humhub/modules/admin/jobs/CleanupLog.php @@ -8,7 +8,7 @@ namespace humhub\modules\admin\jobs; -use humhub\components\queue\ActiveJob; +use humhub\modules\queue\ActiveJob; use humhub\modules\admin\models\Log; /** diff --git a/protected/humhub/modules/admin/messages/de/controllers_PendingRegistrationsController.php b/protected/humhub/modules/admin/messages/de/controllers_PendingRegistrationsController.php new file mode 100644 index 0000000000..6670d2adf8 --- /dev/null +++ b/protected/humhub/modules/admin/messages/de/controllers_PendingRegistrationsController.php @@ -0,0 +1,22 @@ + '', + 'Resend invitation email' => '', +]; diff --git a/protected/humhub/modules/admin/models/SpaceSearch.php b/protected/humhub/modules/admin/models/SpaceSearch.php index 77ba33a9c4..25873b723f 100644 --- a/protected/humhub/modules/admin/models/SpaceSearch.php +++ b/protected/humhub/modules/admin/models/SpaceSearch.php @@ -8,24 +8,32 @@ namespace humhub\modules\admin\models; +use Yii; use yii\base\Model; use yii\data\ActiveDataProvider; use humhub\modules\space\models\Space; - +use humhub\modules\space\models\Membership; /** - * Description of UserSearch + * SpaceSearch for administration * * @author luke */ class SpaceSearch extends Space { + public $freeText; + public $memberCount; + public $owner; + + /** + * @inheritdoc + */ public function rules() { return [ [['id', 'visibility', 'join_policy'], 'integer'], - [['name'], 'safe'], + [['freeText'], 'safe'], ]; } @@ -34,10 +42,28 @@ class SpaceSearch extends Space */ public function scenarios() { - // bypass scenarios() implementation in the parent class + //Bypass scenarios() implementation in the parent class return Model::scenarios(); } + /** + * @inheritdoc + */ + public static function className() + { + return Space::class; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), [ + 'memberCount' => 'Members' + ]); + } + /** * Creates data provider instance with search query applied * @@ -47,7 +73,10 @@ class SpaceSearch extends Space */ public function search($params) { - $query = Space::find(); + $memberCountSubSelect = Membership::find()->select('COUNT(*) as counter')->where('space_id=space.id')->andWhere(['space_membership.status' => Membership::STATUS_MEMBER]); + $query = self::find(); + $query->joinWith(['ownerUser', 'ownerUser.profile']); + $query->addSelect(['space.*', 'memberCount' => $memberCountSubSelect]); $dataProvider = new ActiveDataProvider([ 'query' => $query, @@ -60,22 +89,60 @@ class SpaceSearch extends Space 'name', 'visibility', 'join_policy', + 'memberCount', ] ]); + $dataProvider->sort->attributes['ownerUser.profile.lastname'] = [ + 'asc' => ['profile.lastname' => SORT_ASC], + 'desc' => ['profile.lastname' => SORT_DESC], + ]; + + // default visibility + $this->visibility = Space::VISIBILITY_ALL; $this->load($params); if (!$this->validate()) { - $query->where('0=1'); + $query->emulateExecution(); return $dataProvider; } - $query->andFilterWhere(['id' => $this->id]); - $query->andFilterWhere(['join_policy' => $this->join_policy]); - $query->andFilterWhere(['visibility' => $this->visibility]); - $query->andFilterWhere(['like', 'name', $this->name]); + + // Freetext filters + if (!empty($this->freeText)) { + $query->andWhere([ + 'OR', + ['like', 'space.name', $this->freeText], + ['like', 'user.id', $this->freeText], + ['like', 'user.username', $this->freeText], + ['like', 'user.email', $this->freeText], + ['like', 'profile.firstname', $this->freeText], + ['like', 'profile.lastname', $this->freeText] + ]); + } + + if ($this->visibility == Space::VISIBILITY_NONE) { + $query->andFilterWhere(['space.visibility' => Space::VISIBILITY_NONE]); + } else { + $query->andWhere([ + 'OR', + ['space.visibility' => Space::VISIBILITY_REGISTERED_ONLY], + ['space.visibility' => Space::VISIBILITY_ALL] + ]); + } return $dataProvider; } + public function getVisibilityAttributes() + { + $countPublic = Space::find()->where(['visibility' => Space::VISIBILITY_ALL])->orWhere(['visibility' => Space::VISIBILITY_REGISTERED_ONLY])->count(); + $countPrivate = Space::find()->where(['visibility' => Space::VISIBILITY_NONE])->count(); + + return [ + Space::VISIBILITY_REGISTERED_ONLY => Yii::t('SpaceModule.base', 'Public') . ' (' . $countPublic . ')', + Space::VISIBILITY_NONE => Yii::t('SpaceModule.base', 'Private') . ' (' . $countPrivate . ')', + ]; + } + } diff --git a/protected/humhub/modules/admin/models/UserSearch.php b/protected/humhub/modules/admin/models/UserSearch.php index bf2c26fb8b..212bf25926 100644 --- a/protected/humhub/modules/admin/models/UserSearch.php +++ b/protected/humhub/modules/admin/models/UserSearch.php @@ -8,6 +8,7 @@ namespace humhub\modules\admin\models; +use Yii; use yii\base\Model; use yii\data\ActiveDataProvider; use humhub\modules\user\models\User; @@ -20,19 +21,33 @@ use humhub\modules\user\models\User; class UserSearch extends User { + /** + * @var \humhub\modules\user\components\ActiveQueryUser + */ public $query; + /** + * @var string a free text search + */ + public $freeText; + + /** + * @inheritdoc + */ public function attributes() { // add related fields to searchable attributes return array_merge(parent::attributes(), ['profile.firstname', 'profile.lastname']); } + /** + * @inheritdoc + */ public function rules() { return [ - [['id'], 'integer'], - [['username', 'email', 'created_at', 'profile.firstname', 'profile.lastname', 'last_login'], 'safe'], + [['id', 'status'], 'integer'], + [['username', 'email', 'created_at', 'profile.firstname', 'profile.lastname', 'last_login', 'freeText'], 'safe'], ]; } @@ -55,38 +70,62 @@ class UserSearch extends User public function search($params) { $query = ($this->query == null) ? User::find()->joinWith('profile') : $this->query; - + /* @var $query \humhub\modules\user\components\ActiveQueryUser */ + $dataProvider = new ActiveDataProvider([ 'query' => $query, 'pagination' => ['pageSize' => 50], ]); - $dataProvider->setSort([ 'attributes' => [ 'id', 'username', 'email', - 'last_login', + 'last_login', 'profile.firstname', 'profile.lastname', 'created_at', ] ]); + $dataProvider->sort->defaultOrder = ['id' => SORT_DESC]; $this->load($params); if (!$this->validate()) { - $query->where('0=1'); + $query->emulateExecution(); + return $dataProvider; + } + + + $query->joinWith(['profile']); + + + // Freetext filters + if (!empty($this->freeText)) { + $query->andWhere([ + 'OR', + ['like', 'user.id', $this->freeText], + ['like', 'user.username', $this->freeText], + ['like', 'user.email', $this->freeText], + ['like', 'profile.firstname', $this->freeText], + ['like', 'profile.lastname', $this->freeText] + ]); + + if (!empty($this->status)) { + $query->andFilterWhere(['user.status' => $this->status]); + } return $dataProvider; } $query->andFilterWhere(['id' => $this->id]); + $query->andFilterWhere(['user.status' => $this->status]); $query->andFilterWhere(['like', 'user.id', $this->id]); $query->andFilterWhere(['like', 'user.username', $this->username]); $query->andFilterWhere(['like', 'user.email', $this->email]); $query->andFilterWhere(['like', 'profile.firstname', $this->getAttribute('profile.firstname')]); $query->andFilterWhere(['like', 'profile.lastname', $this->getAttribute('profile.lastname')]); + if ($this->getAttribute('last_login') != "") { try { $last_login = \humhub\libs\DateHelper::parseDateTime($this->getAttribute('last_login')); @@ -94,8 +133,8 @@ class UserSearch extends User $query->andWhere([ '=', new \yii\db\Expression("DATE(last_login)"), - new \yii\db\Expression("DATE(:last_login)", [':last_login'=>$last_login]) - ]); + new \yii\db\Expression("DATE(:last_login)", [':last_login' => $last_login]) + ]); } catch (InvalidParamException $e) { // do not change the query if the date is wrong formatted } @@ -104,4 +143,17 @@ class UserSearch extends User return $dataProvider; } + public function getStatusAttributes() + { + $countActive = User::find()->where(['user.status' => User::STATUS_ENABLED])->count(); + $countDisabled = User::find()->where(['user.status' => User::STATUS_DISABLED])->count(); + $countSoftDeleted = User::find()->where(['user.status' => User::STATUS_SOFT_DELETED])->count(); + + return [ + User::STATUS_ENABLED => Yii::t('AdminModule.user', 'Active users') . ' (' . $countActive . ')', + User::STATUS_DISABLED => Yii::t('AdminModule.user', 'Disabled users') . ' (' . $countDisabled . ')', + User::STATUS_SOFT_DELETED => Yii::t('AdminModule.user', 'Deleted users') . ' (' . $countSoftDeleted . ')', + ]; + } + } diff --git a/protected/humhub/modules/admin/models/forms/CacheSettingsForm.php b/protected/humhub/modules/admin/models/forms/CacheSettingsForm.php index ef465285ad..2e74c4e342 100644 --- a/protected/humhub/modules/admin/models/forms/CacheSettingsForm.php +++ b/protected/humhub/modules/admin/models/forms/CacheSettingsForm.php @@ -57,11 +57,17 @@ class CacheSettingsForm extends Model */ public function getTypes() { - return [ + $cacheTypes = [ 'yii\caching\DummyCache' => \Yii::t('AdminModule.forms_CacheSettingsForm', 'No caching'), 'yii\caching\FileCache' => \Yii::t('AdminModule.forms_CacheSettingsForm', 'File'), 'yii\caching\ApcCache' => \Yii::t('AdminModule.forms_CacheSettingsForm', 'APC(u)'), ]; + + if (isset(Yii::$app->redis)) { + $cacheTypes['yii\redis\Cache'] = \Yii::t('AdminModule.forms_CacheSettingsForm', 'Redis'); + } + + return $cacheTypes; } /** diff --git a/protected/humhub/modules/admin/models/forms/UserDeleteForm.php b/protected/humhub/modules/admin/models/forms/UserDeleteForm.php new file mode 100644 index 0000000000..0ec72d0950 --- /dev/null +++ b/protected/humhub/modules/admin/models/forms/UserDeleteForm.php @@ -0,0 +1,137 @@ +user->status == User::STATUS_SOFT_DELETED) { + $this->deleteContributions = true; + } + } + + /** + * @inheritdoc + */ + public function rules() + { + $rules = []; + $rules[] = [['deleteSpaces'], 'boolean']; + + if ($this->user->status != User::STATUS_SOFT_DELETED) { + $rules[] = [['deleteContributions'], 'boolean']; + } + + return $rules; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array( + 'deleteContributions' => Yii::t('AdminModule.user', 'Delete all contributions of this user'), + 'deleteSpaces' => Yii::t('AdminModule.user', 'Delete spaces which are owned by this user'), + ); + } + + /** + * @inheritdoc + */ + public function attributeHints() + { + return array( + 'deleteContributions' => Yii::t('AdminModule.user', 'Using this option any contributions (e.g. contents, comments or likes) of this user will be irrevocably deleted.'), + 'deleteSpaces' => Yii::t('AdminModule.user', 'If this option is not selected, the ownership of the spaces will be transferred to your account.'), + ); + } + + /** + * Perform user deletion + * @since 1.3 + */ + public function performDelete() + { + if (!$this->validate()) { + return false; + } + + // Handle owned spaces by the deleted user + $ownedSpaces = MembershipHelper::getOwnSpaces($this->user); + if (count($ownedSpaces) !== 0 && empty($this->deleteSpaces)) { + foreach ($ownedSpaces as $space) { + $space->addMember(Yii::$app->user->id); + $space->setSpaceOwner(Yii::$app->user->id); + } + } + + if (empty($this->deleteContributions)) { + Yii::$app->queue->push(new SoftDeleteUser(['user_id' => $this->user->id])); + } else { + Yii::$app->queue->push(new DeleteUser(['user_id' => $this->user->id])); + } + + return true; + } + + /** + * Returns all spaces which are owned by the user + * + * @return Space[] the spaces + */ + public function getOwningSpaces() + { + if ($this->_spaces !== null) { + return $this->_spaces; + } + + $this->_spaces = MembershipHelper::getOwnSpaces($this->user); + return $this->_spaces; + } + +} diff --git a/protected/humhub/modules/admin/views/approval/index.php b/protected/humhub/modules/admin/views/approval/index.php index 5af24bad2d..03875e6b11 100644 --- a/protected/humhub/modules/admin/views/approval/index.php +++ b/protected/humhub/modules/admin/views/approval/index.php @@ -3,6 +3,8 @@ use yii\helpers\Url; use yii\helpers\Html; use humhub\widgets\GridView; +use humhub\modules\user\grid\ImageColumn; +use humhub\modules\user\grid\DisplayNameColumn; ?>
    @@ -13,17 +15,14 @@ use humhub\widgets\GridView;
    $dataProvider, - 'filterModel' => $searchModel, 'columns' => [ - 'username', + ['class' => ImageColumn::class], + ['class' => DisplayNameColumn::class], 'email', - 'profile.firstname', - 'profile.lastname', - 'profile.lastname', + 'created_at', [ - 'header' => Yii::t('AdminModule.views_approval_index', 'Actions'), 'class' => 'yii\grid\ActionColumn', 'options' => ['width' => '150px'], 'buttons' => [ @@ -31,10 +30,10 @@ use humhub\widgets\GridView; return; }, 'delete' => function($url, $model) { - return Html::a('Decline', Url::toRoute(['decline', 'id' => $model->id]), ['class' => 'btn btn-danger btn-sm', 'data-ui-loader' => '']); + return Html::a('Decline', Url::to(['decline', 'id' => $model->id]), ['class' => 'btn btn-danger btn-sm', 'data-ui-loader' => '']); }, 'update' => function($url, $model) { - return Html::a('Approve', Url::toRoute(['approve', 'id' => $model->id]), ['class' => 'btn btn-primary btn-sm', 'data-ui-loader' => '']); + return Html::a('Approve', Url::to(['approve', 'id' => $model->id]), ['class' => 'btn btn-primary btn-sm', 'data-ui-loader' => '']); }, ], ], diff --git a/protected/humhub/modules/admin/views/authentication/authentication_ldap.php b/protected/humhub/modules/admin/views/authentication/authentication_ldap.php index 24da91a577..a916917520 100644 --- a/protected/humhub/modules/admin/views/authentication/authentication_ldap.php +++ b/protected/humhub/modules/admin/views/authentication/authentication_ldap.php @@ -1,5 +1,4 @@ beginContent('@admin/views/authentication/_authenticationLayout.php') ?>
    - +

    - -
    $errorMessage]); ?>
    + +
    + $errorMessage] + ) ?> +
    -
    +
    + +
    -
    $userCount]); ?>
    +
    + $userCount] + ) ?> +
    - 'authentication-settings-form']); ?> + 'authentication-settings-form', + 'fieldConfig' => function ($model, $attribute) { + return [ + 'inputOptions' => [ + 'class' => 'form-control', + 'readonly' => Setting::IsFixed('auth.ldap.' . $attribute, 'user') + ], + ]; + } + ]) ?> - field($model, 'enabled')->checkbox(['readonly' => Setting::IsFixed('auth.ldap.enabled', 'user')]); ?> + field($model, 'enabled')->checkbox() ?>
    - field($model, 'hostname')->textInput(['readonly' => Setting::IsFixed('auth.ldap.hostname', 'user')]); ?> - field($model, 'port')->textInput(['readonly' => Setting::IsFixed('auth.ldap.port', 'user')]); ?> - field($model, 'encryption')->dropDownList($model->encryptionTypes, ['readonly' => Setting::IsFixed('auth.ldap.encryption', 'user')]); ?> - field($model, 'username')->textInput(['readonly' => Setting::IsFixed('auth.ldap.username', 'user')]); ?> - field($model, 'password')->passwordInput(['readonly' => Setting::IsFixed('auth.ldap.password', 'user')]); ?> - field($model, 'baseDn')->textInput(['readonly' => Setting::IsFixed('auth.ldap.baseDn', 'user')]); ?> - field($model, 'loginFilter')->textArea(['readonly' => Setting::IsFixed('auth.ldap.loginFilter', 'user')]); ?> - field($model, 'userFilter')->textArea(['readonly' => Setting::IsFixed('auth.ldap.userFilter', 'user')]); ?> - field($model, 'usernameAttribute')->textInput(['readonly' => Setting::IsFixed('auth.ldap.usernameAttribute', 'user')]); ?> - field($model, 'emailAttribute')->textInput(['readonly' => Setting::IsFixed('auth.ldap.emailAttribute', 'user')]); ?> - field($model, 'idAttribute')->textInput(['readonly' => Setting::IsFixed('auth.ldap.idAttribute', 'user')]); ?> - field($model, 'refreshUsers')->checkbox(['readonly' => Setting::IsFixed('auth.ldap.refreshUsers', 'user')]); ?> - + field($model, 'hostname')->textInput() ?> + field($model, 'port')->textInput() ?> + field($model, 'encryption')->dropDownList($model->encryptionTypes) ?> + field($model, 'username')->textInput() ?> + field($model, 'password')->passwordInput() ?> + field($model, 'baseDn')->textInput() ?> + field($model, 'loginFilter')->textArea() ?> + field($model, 'userFilter')->textArea() ?> + field($model, 'usernameAttribute')->textInput() ?> + field($model, 'emailAttribute')->textInput() ?> + field($model, 'idAttribute')->textInput() ?> + field($model, 'refreshUsers')->checkbox() ?>
    - 'btn btn-primary', 'data-ui-loader' => ""]); ?> - - + 'btn btn-primary', 'data-ui-loader' => ''] + ) ?> + + +
    -endContent(); ?> \ No newline at end of file +endContent() ?> \ No newline at end of file diff --git a/protected/humhub/modules/admin/views/group/index.php b/protected/humhub/modules/admin/views/group/index.php index fe897ce1b4..bb1ad045f6 100644 --- a/protected/humhub/modules/admin/views/group/index.php +++ b/protected/humhub/modules/admin/views/group/index.php @@ -5,6 +5,10 @@ use yii\helpers\Html; use humhub\widgets\GridView; ?>
    +
    +   ' . Yii::t('AdminModule.views_groups_index', "Create new group"), Url::to(['edit']), ['class' => 'btn btn-sm btn-success']); ?> +
    +

    @@ -15,9 +19,6 @@ use humhub\widgets\GridView;
    -
    -   ' . Yii::t('AdminModule.views_groups_index', "Create new group"), Url::to(['edit']), ['class' => 'btn btn-success']); ?> -
    Yii::t('AdminModule.views_group_index', 'Actions'), - 'class' => 'yii\grid\ActionColumn', - 'options' => ['width' => '80px'], - 'buttons' => [ - 'view' => function() { - return; - }, - 'delete' => function() { - return; - }, - 'update' => function($url, $model) { - return Html::a('', Url::toRoute(['edit', 'id' => $model->id]), ['class' => 'btn btn-primary btn-xs tt']); - }, - ], + 'class' => \humhub\libs\ActionColumn::class, + 'actions' => [ + Yii::t('AdminModule.user', 'Settings') => ['edit'], + '---', + Yii::t('AdminModule.user', "Permissions") => ['manage-permissions'], + Yii::t('AdminModule.user', "Members") => ['manage-group-users'], + ] ], ], ]); diff --git a/protected/humhub/modules/admin/views/group/members.php b/protected/humhub/modules/admin/views/group/members.php index 9c1d0b3866..0243535ab6 100644 --- a/protected/humhub/modules/admin/views/group/members.php +++ b/protected/humhub/modules/admin/views/group/members.php @@ -4,90 +4,100 @@ use yii\helpers\Url; use yii\helpers\Html; use yii\widgets\ActiveForm; use humhub\widgets\GridView; +use humhub\modules\user\grid\ImageColumn; +use humhub\modules\user\grid\DisplayNameColumn; \humhub\modules\admin\assets\AdminGroupAsset::register($this); - ?> beginContent('@admin/views/group/_manageLayout.php', ['group' => $group]) ?>
    - ['/admin/group/add-members']]); ?> -
    -
    - $addGroupMemberForm, - 'attribute' => 'userGuids', - 'url' => Url::to(['/admin/group/new-member-search', 'id' => $group->id]), - 'placeholder' => Yii::t('AdminModule.views_group_manageGroupUser', 'Add new members...'), - 'focus' => true, - ]) - ?> - $group->id]) ?> - - - +
    +
    + ['/admin/group/add-members']]); ?> +
    + $addGroupMemberForm, + 'attribute' => 'userGuids', + 'url' => Url::to(['/admin/group/new-member-search', 'id' => $group->id]), + 'placeholder' => Yii::t('AdminModule.views_group_manageGroupUser', 'Add new members...'), + 'focus' => true, + ]) + ?> + $group->id]) ?> + + + +
    + +
    +
    + 'get']); ?> +
    + 'form-control', 'placeholder' => Yii::t('AdminModule.user', 'Search by name, email or id.')]); ?> + + + +
    +
    -
    $dataProvider, - 'filterModel' => $searchModel, - 'columns' => [ - [ - 'attribute' => 'id', - 'options' => ['style' => 'width:40px;'], - 'format' => 'raw', - 'value' => function($data) { - return $data->id; + $actionUrl = Url::to(['edit-manager-role']); + echo GridView::widget([ + 'dataProvider' => $dataProvider, + #'filterModel' => $searchModel, + 'summary' => '', + 'columns' => [ + ['class' => ImageColumn::class], + ['class' => DisplayNameColumn::class], + [ + 'attribute' => 'created_at', + 'label' => Yii::t('AdminModule.user', 'Member since'), + 'format' => 'datetime', + 'options' => ['style' => 'width:160px; min-width:160px;'], + ], + [ + 'attribute' => 'is_manager', + 'visible' => $isManagerApprovalSetting, + 'label' => Yii::t('AdminModule.user', 'Group Manager'), + 'format' => 'raw', + 'value' => function ($data) use ($group, $actionUrl) { + $isManager = $group->isManager($data); + $yesSelected = ($isManager) ? 'selected' : ''; + $noSelected = ($isManager) ? '' : 'selected'; + $result = ''; - $result .= ''; - $result .= ''; - return $result; + 'update' => function($url, $model) use ($group) { + return false; + }, + 'delete' => function($url, $model) use ($group) { + return Html::a('', '#', [ + 'data-action-click' => 'admin.group.removeMember', + 'data-action-url' => Url::to(['remove-group-user', 'id' => $group->id, 'userId' => $model->id]), + 'title' => Yii::t('AdminModule.views_group_manageGroupUser', 'Remove from group'), + 'class' => 'btn btn-danger btn-xs tt']); } ], - [ - 'header' => Yii::t('AdminModule.views_user_index', 'Actions'), - 'class' => 'yii\grid\ActionColumn', - 'options' => ['style' => 'width:80px; min-width:80px;'], - 'buttons' => [ - 'view' => function($url, $model) { - return false; - }, - 'update' => function($url, $model) use ($group) { - return false; - }, - 'delete' => function($url, $model) use ($group) { - return Html::a('', '#', [ - 'data-action-click' => 'admin.group.removeMember', - 'data-action-url' => Url::to(['remove-group-user', 'id' => $group->id, 'userId' => $model->id]), - 'title' => Yii::t('AdminModule.views_group_manageGroupUser', 'Remove from group'), - 'class' => 'btn btn-danger btn-xs tt']); - } - ], - ], ], - ] - );?> + ], + ] + ); + ?>
    endContent(); ?> \ No newline at end of file diff --git a/protected/humhub/modules/admin/views/pending-registrations/index.php b/protected/humhub/modules/admin/views/pending-registrations/index.php index b1c3db6149..24beeb328d 100644 --- a/protected/humhub/modules/admin/views/pending-registrations/index.php +++ b/protected/humhub/modules/admin/views/pending-registrations/index.php @@ -7,42 +7,65 @@ use yii\helpers\Url; ?>
    - Yii::t('AdminModule.base', 'Back to user overview'), 'class' => 'pull-right']); ?> + Yii::t('AdminModule.base', 'Back to user overview'), 'class' => 'pull-right'] + ); ?>

    - +
    - $dataProvider, 'filterModel' => $searchModel, - 'columns' => ['email', - 'originator.username', - 'language', - 'created_at', + 'columns' => [ - 'attribute' => 'source', - 'filter' => \yii\helpers\Html::activeDropDownList($searchModel, 'source', array_merge(['' => ''], $types)), - 'options' => ['width' => '40px'], - 'format' => 'raw', - 'value' => function($data) use ($types) { - if (isset($types[$data->source])) { - return $types[$data->source]; - } - return Html::encode($data->source); - }, - ],] - ]); - ?> + 'email', + 'originator.username', + 'language', + 'created_at', + [ + 'attribute' => 'source', + 'filter' => Html::activeDropDownList($searchModel, 'source', array_merge(['' => ''], $types)), + 'options' => ['width' => '40px'], + 'format' => 'raw', + 'value' => function ($data) use ($types) { + return isset($types[$data->source]) ? $types[$data->source] : Html::encode($data->source); + }, + ], + [ + 'header' => Yii::t('AdminModule.views_user_index', 'Actions'), + 'class' => 'yii\grid\ActionColumn', + 'template' => '{resend}', + 'options' => ['style' => 'width:80px; min-width:80px;'], + 'buttons' => [ + 'resend' => function ($url, $model, $key) { + return Html::a( + '', + Url::to(['resend', 'id' => $model->id]), + ['class' => 'btn btn-primary btn-xs tt'] + ); + }, + ], + ], + ] + ]); ?>
    \ No newline at end of file diff --git a/protected/humhub/modules/admin/views/pending-registrations/resend.php b/protected/humhub/modules/admin/views/pending-registrations/resend.php new file mode 100644 index 0000000000..0d195d6b83 --- /dev/null +++ b/protected/humhub/modules/admin/views/pending-registrations/resend.php @@ -0,0 +1,32 @@ + +
    +

    +
    + + $model, + 'attributes' => [ + 'email:email', + 'created_at:datetime', + 'updated_at:datetime', + ], + ]); ?> + +
    +
    + + $model->id]), + ['class' => 'btn btn-danger', 'data-method' => 'POST'] + ); ?> + 'btn btn-primary pull-right'] + ); ?> +
    \ No newline at end of file diff --git a/protected/humhub/modules/admin/views/space/index.php b/protected/humhub/modules/admin/views/space/index.php index 08c422b3f1..204fbcdcda 100644 --- a/protected/humhub/modules/admin/views/space/index.php +++ b/protected/humhub/modules/admin/views/space/index.php @@ -1,81 +1,56 @@ +  ' . Yii::t('AdminModule.space', 'Add new space'), ['/space/create'], ['class' => 'btn btn-sm btn-success pull-right', 'data-target' => '#globalModal']); ?> +

    +
    + 'get']); ?> +
    +
    +
    + 'form-control', 'placeholder' => Yii::t('AdminModule.space', 'Search by name, description, id or owner.')]); ?> + + + +
    +
    +
    + 'form-control', 'onchange' => 'this.form.submit()']); ?> +
    +
    + + +
    -   ' . Yii::t('AdminModule.space', 'Add new space'), ['/space/create'], ['class' => 'btn btn-success pull-right', 'data-target' => '#globalModal']); ?> - Yii::t('SpaceModule.base', 'Private (Invisible)'), - Space::VISIBILITY_REGISTERED_ONLY => Yii::t('SpaceModule.base', 'Public (Visible)'), - Space::VISIBILITY_ALL => 'All', - ]; - - $joinPolicies = [ - Space::JOIN_POLICY_NONE => Yii::t('SpaceModule.base', 'Only by invite'), - Space::JOIN_POLICY_APPLICATION => Yii::t('SpaceModule.base', 'Invite and request'), - Space::JOIN_POLICY_FREE => Yii::t('SpaceModule.base', 'Everyone can enter'), - ]; - - echo SpaceGridView::widget([ + $dataProvider, - 'filterModel' => $searchModel, + 'summary' => '', 'columns' => [ + ['class' => SpaceImageColumn::class], + ['class' => SpaceTitleColumn::class], + 'memberCount', + ['class' => \humhub\modules\user\grid\ImageColumn::class, 'userAttribute' => 'ownerUser'], [ - 'attribute' => 'id', - 'options' => ['width' => '40px'], - 'format' => 'raw', - 'value' => function($data) { - return $data->id; - }, - ], - 'name', - [ - 'attribute' => 'visibility', - 'filter' => \yii\helpers\Html::activeDropDownList($searchModel, 'visibility', array_merge(['' => ''], $visibilities)), - 'options' => ['width' => '40px'], - 'format' => 'raw', - 'value' => function($data) use ($visibilities) { - if (isset($visibilities[$data->visibility])) - return $visibilities[$data->visibility]; - return Html::encode($data->visibility); - }, - ], - [ - 'attribute' => 'join_policy', - 'options' => ['width' => '40px'], - 'filter' => \yii\helpers\Html::activeDropDownList($searchModel, 'join_policy', array_merge(['' => ''], $joinPolicies)), - 'format' => 'raw', - 'value' => function($data) use ($joinPolicies) { - if (isset($joinPolicies[$data->join_policy])) - return $joinPolicies[$data->join_policy]; - return Html::encode($data->join_policy); - }, - ], - [ - 'header' => Yii::t('AdminModule.views_space_index', 'Actions'), - 'class' => 'yii\grid\ActionColumn', - 'options' => ['width' => '80px'], - 'buttons' => [ - 'view' => function($url, $model) { - return Html::a('', $model->getUrl(), ['class' => 'btn btn-primary btn-xs tt']); - }, - 'update' => function($url, $model) { - return Html::a('', $model->createUrl('/space/manage'), ['class' => 'btn btn-primary btn-xs tt']); - }, - 'delete' => function($url, $model) { - return Html::a('', $model->createUrl('/space/manage/default/delete'), ['class' => 'btn btn-danger btn-xs tt']); - } - ], + 'attribute' => 'ownerUser.profile.lastname', + 'class' => \humhub\modules\user\grid\DisplayNameColumn::class, + 'userAttribute' => 'ownerUser', + 'label' => 'Owner' ], + ['class' => SpaceActionColumn::class], ], ]); ?> diff --git a/protected/humhub/modules/admin/views/user/add.php b/protected/humhub/modules/admin/views/user/add.php index 837ed8b59e..9c6855c87f 100644 --- a/protected/humhub/modules/admin/views/user/add.php +++ b/protected/humhub/modules/admin/views/user/add.php @@ -7,7 +7,12 @@ humhub\assets\TabbedFormAsset::register($this);
    - Yii::t('AdminModule.base', 'Back to overview'), 'class' => 'pull-right']); ?> + +
    + Yii::t('AdminModule.base', 'Back to overview')]); ?> +   ' . Yii::t('AdminModule.views_user_index', 'Send invite'), ['/user/invite'], ['class' => 'btn btn-success', 'data-target' => '#globalModal']); ?> +
    +


    diff --git a/protected/humhub/modules/admin/views/user/delete.php b/protected/humhub/modules/admin/views/user/delete.php index 68a0f185ab..c5bc5d8fd5 100644 --- a/protected/humhub/modules/admin/views/user/delete.php +++ b/protected/humhub/modules/admin/views/user/delete.php @@ -1,36 +1,56 @@
    -

    +


    -

    +

    -
      -
    • deleted.'); ?>
    • -
    • you will automatically become owner of these spaces.'); ?>
    • -
    +
    +
    + $model->user, 'width' => 38, 'link' => true]); ?> +
    +
    +

    user); ?>

    + user->email) ?> +
    +
    +
    +

    + getOwningSpaces()) !== 0): ?> +
    +

    + getOwningSpaces() as $space): ?> +
    +
    + $space, 'width' => 38, 'link' => true]); ?> +
    +
    +

    + $space->getMemberships()->count()]); ?> +
    +
    + +
    + -
    +
    + + field($model, 'deleteContributions')->checkbox(['disabled' => !$model->isAttributeSafe('deleteContributions')]); ?> + getOwningSpaces()) !== 0): ?> + field($model, 'deleteSpaces')->checkbox(); ?> + - $model, - 'attributes' => [ - 'username', - 'profile.firstname', - 'profile.lastname', - 'email:email', - 'created_at:datetime', - ], - ]); - ?> - -
    -
    - - $model->id, 'doit' => 2]), ['class' => 'btn btn-danger', 'data-method' => 'POST']); ?> - $model->id]), ['class' => 'btn btn-primary pull-right']); ?> +
    +
    + 'btn btn-danger', 'data-ui-loader' => '']); ?> + $model->user->id]), ['class' => 'btn btn-primary pull-right']); ?> +
    \ No newline at end of file diff --git a/protected/humhub/modules/admin/views/user/index.php b/protected/humhub/modules/admin/views/user/index.php deleted file mode 100644 index 158d097839..0000000000 --- a/protected/humhub/modules/admin/views/user/index.php +++ /dev/null @@ -1,71 +0,0 @@ - - -
    -

    -
    - -
    -
    -
    -   ' . Yii::t('AdminModule.views_user_index', 'Add new user'), ['/admin/user/add'], ['class' => 'btn btn-success', 'data-ui-loader'=>'']); ?> -   ' . Yii::t('AdminModule.views_user_index', 'Send invite'), ['/user/invite'], ['class' => 'btn btn-success', 'data-target' => '#globalModal']); ?> - link(Url::to(['/admin/pending-registrations'])) - ->visible(\humhub\modules\user\models\Invite::find()->count() > 0 && Yii::$app->user->can([new \humhub\modules\admin\permissions\ManageUsers(), new \humhub\modules\admin\permissions\ManageGroups()])); ?> -
    - - $dataProvider, - 'filterModel' => $searchModel, - 'columns' => [ - [ - 'attribute' => 'id', - 'options' => ['style' => 'width:40px;'], - 'format' => 'raw', - 'value' => function($data) { - return $data->id; - }, - ], - 'username', - 'email', - 'profile.firstname', - 'profile.lastname', - [ - 'attribute' => 'last_login', - 'label' => Yii::t('AdminModule.views_user_index', 'Last login'), - 'filter' => \yii\jui\DatePicker::widget([ - 'model' => $searchModel, - 'attribute' => 'last_login', - 'options' => ['style' => 'width:86px;', 'class' => 'form-control'], - ]), - 'value' => function ($data) { - return ($data->last_login == NULL) ? Yii::t('AdminModule.views_user_index', 'never') : Yii::$app->formatter->asDate($data->last_login); - } - ], - [ - 'header' => Yii::t('AdminModule.views_user_index', 'Actions'), - 'class' => 'yii\grid\ActionColumn', - 'options' => ['style' => 'width:80px; min-width:80px;'], - 'buttons' => [ - 'view' => function($url, $model) { - return Html::a('', $model->getUrl(), ['class' => 'btn btn-primary btn-xs tt']); - }, - 'update' => function($url, $model) { - return Html::a('', Url::toRoute(['edit', 'id' => $model->id]), ['class' => 'btn btn-primary btn-xs tt']); - }, - 'delete' => function($url, $model) { - return Html::a('', Url::toRoute(['delete', 'id' => $model->id]), ['class' => 'btn btn-danger btn-xs tt']); - } - ], - ], - ], - ]); - ?> -
    -
    \ No newline at end of file diff --git a/protected/humhub/modules/admin/views/user/list.php b/protected/humhub/modules/admin/views/user/list.php new file mode 100644 index 0000000000..b36b45ea0d --- /dev/null +++ b/protected/humhub/modules/admin/views/user/list.php @@ -0,0 +1,69 @@ + + +
    + +
    +   ' . Yii::t('AdminModule.user', 'Add new user'), ['/admin/user/add'], ['class' => 'btn btn-success btn-sm', 'data-ui-loader' => '']); ?> +
    + +

    +
    + +
    + +
    + + 'get']); ?> +
    +
    +
    + 'form-control', 'placeholder' => Yii::t('AdminModule.user', 'Search by name, email or id.')]); ?> + + + +
    +
    +
    + 'form-control', 'onchange' => 'this.form.submit()']); ?> +
    +
    + + +
    + $dataProvider, + 'summary' => '', + 'columns' => [ + ['class' => ImageColumn::class], + ['class' => DisplayNameColumn::class], + 'email', + [ + 'attribute' => 'last_login', + 'label' => Yii::t('AdminModule.user', 'Last login'), + 'options' => ['style' => 'width:120px;'], + 'value' => function ($data) { + return ($data->last_login == NULL) ? Yii::t('AdminModule.user', 'never') : Yii::$app->formatter->asDate($data->last_login); + } + ], + ['class' => UserActionColumn::class], + ], + ]); + ?> +
    + +
    + link(Url::to(['/admin/pending-registrations']))->right()->sm(); ?> + +
    \ No newline at end of file diff --git a/protected/humhub/modules/admin/widgets/GroupManagerMenu.php b/protected/humhub/modules/admin/widgets/GroupManagerMenu.php index fde3a42044..1bde067cd3 100644 --- a/protected/humhub/modules/admin/widgets/GroupManagerMenu.php +++ b/protected/humhub/modules/admin/widgets/GroupManagerMenu.php @@ -2,7 +2,7 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2015 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ @@ -33,19 +33,19 @@ class GroupManagerMenu extends \humhub\widgets\BaseMenu public function init() { $this->addItem([ - 'label' => Yii::t('AdminModule.views_user_index', 'Settings'), + 'label' => Yii::t('AdminModule.user', 'Settings'), 'url' => Url::toRoute(['/admin/group/edit', 'id' => $this->group->id]), 'sortOrder' => 100, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group' && Yii::$app->controller->action->id == 'edit'), ]); $this->addItem([ - 'label' => Yii::t('AdminModule.views_groups_index', "Permissions"), + 'label' => Yii::t('AdminModule.user', "Permissions"), 'url' => Url::toRoute(['/admin/group/manage-permissions', 'id' => $this->group->id]), 'sortOrder' => 200, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group' && Yii::$app->controller->action->id == 'manage-permissions'), ]); $this->addItem([ - 'label' => Yii::t('AdminModule.views_groups_index', "Members"), + 'label' => Yii::t('AdminModule.user', "Members"), 'url' => Url::toRoute(['/admin/group/manage-group-users', 'id' => $this->group->id]), 'sortOrder' => 200, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group' && Yii::$app->controller->action->id == 'manage-group-users'), diff --git a/protected/humhub/modules/admin/widgets/UserMenu.php b/protected/humhub/modules/admin/widgets/UserMenu.php index f51e013f18..03b7fbfe1c 100644 --- a/protected/humhub/modules/admin/widgets/UserMenu.php +++ b/protected/humhub/modules/admin/widgets/UserMenu.php @@ -8,21 +8,25 @@ namespace humhub\modules\admin\widgets; -use humhub\modules\admin\models\UserApprovalSearch; -use humhub\modules\user\models\Invite; use Yii; use yii\helpers\Url; +use humhub\modules\admin\models\UserApprovalSearch; +use humhub\modules\admin\permissions\ManageUsers; +use humhub\modules\admin\permissions\ManageGroups; +use humhub\modules\admin\permissions\ManageSettings; +use humhub\modules\user\models\Invite; +use humhub\widgets\BaseMenu; /** * User Administration Menu * * @author Basti */ -class UserMenu extends \humhub\widgets\BaseMenu +class UserMenu extends BaseMenu { - public $template = "@humhub/widgets/views/tabMenu"; - public $type = "adminUserSubNavigation"; + public $template = '@humhub/widgets/views/tabMenu'; + public $type = 'adminUserSubNavigation'; public function init() { @@ -32,8 +36,8 @@ class UserMenu extends \humhub\widgets\BaseMenu 'sortOrder' => 100, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && (Yii::$app->controller->id == 'user' || Yii::$app->controller->id == 'pending-registrations')), 'isVisible' => Yii::$app->user->can([ - new \humhub\modules\admin\permissions\ManageUsers(), - new \humhub\modules\admin\permissions\ManageGroups(), + new ManageUsers(), + new ManageGroups(), ]) ]); @@ -43,7 +47,7 @@ class UserMenu extends \humhub\widgets\BaseMenu 'sortOrder' => 200, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'authentication'), 'isVisible' => Yii::$app->user->can([ - new \humhub\modules\admin\permissions\ManageSettings() + new ManageSettings() ]) ]); @@ -55,8 +59,8 @@ class UserMenu extends \humhub\widgets\BaseMenu 'sortOrder' => 300, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'approval'), 'isVisible' => Yii::$app->user->can([ - new \humhub\modules\admin\permissions\ManageUsers(), - new \humhub\modules\admin\permissions\ManageGroups() + new ManageUsers(), + new ManageGroups() ]) ]); } @@ -67,7 +71,7 @@ class UserMenu extends \humhub\widgets\BaseMenu 'sortOrder' => 400, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'user-profile'), 'isVisible' => Yii::$app->user->can([ - new \humhub\modules\admin\permissions\ManageUsers() + new ManageUsers() ]) ]); @@ -77,7 +81,7 @@ class UserMenu extends \humhub\widgets\BaseMenu 'sortOrder' => 500, 'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group'), 'isVisible' => Yii::$app->user->can( - new \humhub\modules\admin\permissions\ManageGroups() + new ManageGroups() ) ]); diff --git a/protected/humhub/modules/content/Events.php b/protected/humhub/modules/content/Events.php index cbb83255e0..cd1b511971 100644 --- a/protected/humhub/modules/content/Events.php +++ b/protected/humhub/modules/content/Events.php @@ -2,7 +2,7 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2015 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ @@ -10,6 +10,7 @@ namespace humhub\modules\content; use Yii; use humhub\modules\content\models\Content; +use humhub\modules\user\events\UserEvent; /** * Events provides callbacks to handle events. @@ -19,23 +20,43 @@ use humhub\modules\content\models\Content; class Events extends \yii\base\Object { + /** + * Callback when a user is soft deleted. + * + * @param UserEvent $event + */ + public static function onUserSoftDelete(UserEvent $event) + { + // Delete user profile content on soft delete + foreach (Content::findAll(['contentcontainer_id' => $event->user->contentcontainer_id]) as $content) { + $content->delete(); + } + } + + /** + * Callback when a user is completely deleted. + * + * @param \yii\base\Event $event + */ public static function onUserDelete($event) { $user = $event->sender; foreach (Content::findAll(['created_by' => $user->id]) as $content) { $content->delete(); } - return true; } + /** + * Callback when a user is completely deleted. + * + * @param \yii\base\Event $event + */ public static function onSpaceDelete($event) { $space = $event->sender; foreach (Content::findAll(['contentcontainer_id' => $space->contentContainerRecord->id]) as $content) { $content->delete(); } - - return true; } /** diff --git a/protected/humhub/modules/content/components/ContentContainerActiveRecord.php b/protected/humhub/modules/content/components/ContentContainerActiveRecord.php index 0a9a6f8b23..8e4b7351ad 100644 --- a/protected/humhub/modules/content/components/ContentContainerActiveRecord.php +++ b/protected/humhub/modules/content/components/ContentContainerActiveRecord.php @@ -2,33 +2,35 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2016 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ namespace humhub\modules\content\components; use Yii; +use yii\helpers\Url; +use humhub\components\ActiveRecord; use humhub\libs\BasePermission; -use humhub\modules\content\models\Content; use humhub\libs\ProfileBannerImage; use humhub\libs\ProfileImage; use humhub\modules\user\models\User; -use humhub\components\ActiveRecord; +use humhub\modules\content\models\Content; use humhub\modules\content\models\ContentContainer; +use humhub\modules\content\components\ContentContainerModuleManager; /** * ContentContainerActiveRecord for ContentContainer Models e.g. Space or User. * * Required Methods: * - getProfileImage() - * - getUrl() * * @property integer $id * @property integer $visibility * @property integer $contentcontainer_id * @property ContentContainerPermissionManager $permissionManager * @property ContentContainerSettingsManager $settings + * @property ContentContainerModuleManager $moduleManager * * @since 1.0 * @author Luke @@ -41,6 +43,25 @@ abstract class ContentContainerActiveRecord extends ActiveRecord */ protected $permissionManager = null; + /** + * @var ModuleManager + */ + protected $moduleManager = null; + + /** + * The behavior which will be attached to the base controller. + * + * @since 1.3 + * @see \humhub\modules\contentcontainer\components\Controller + * @var string class name of additional the controller behavior + */ + public $controllerBehavior = null; + + /** + * @var string the default route + */ + public $defaultRoute = '/'; + /** * Returns the Profile Image Object for this Content Base * @@ -74,15 +95,17 @@ abstract class ContentContainerActiveRecord extends ActiveRecord /** * Creates url in content container scope. - * E.g. add uguid or sguid parameter to parameters. * * @param string $route * @param array $params * @param boolean|string $scheme */ - public function createUrl($route = null, $params = array(), $scheme = false) + public function createUrl($route = null, $params = [], $scheme = false) { - return ""; + array_unshift($params, ($route !== null) ? $route : $this->defaultRoute); + $params['contentContainer'] = $this; + + return Url::to($params, $scheme); } /** @@ -117,7 +140,7 @@ abstract class ContentContainerActiveRecord extends ActiveRecord { return "Default Wall Output for Class " . get_class($this); } - + public static function findByGuid($token) { return static::findOne(['guid' => $token]); @@ -169,10 +192,14 @@ abstract class ContentContainerActiveRecord extends ActiveRecord */ public function getContentContainerRecord() { + if ($this->hasAttribute('contentcontainer_id')) { + return $this->hasOne(ContentContainer::className(), ['id' => 'contentcontainer_id']); + } + return $this->hasOne(ContentContainer::className(), ['pk' => 'id']) - ->andOnCondition(['class' => self::className()]); + ->andOnCondition(['class' => $this->className()]); } - + /** * Checks if the current user has the given Permission on this ContentContainerActiveRecord. * This is a shortcut for `$this->getPermisisonManager()->can()`. @@ -204,7 +231,7 @@ abstract class ContentContainerActiveRecord extends ActiveRecord */ public function getPermissionManager(User $user = null) { - if($user && !$user->is(Yii::$app->user->getIdentity())) { + if ($user && !$user->is(Yii::$app->user->getIdentity())) { return new ContentContainerPermissionManager([ 'contentContainer' => $this, 'subject' => $user @@ -220,6 +247,23 @@ abstract class ContentContainerActiveRecord extends ActiveRecord ]); } + /** + * Returns a ModuleManager + * + * @since 1.3 + * @param User $user + * @return ModuleManager + */ + public function getModuleManager() + { + + if ($this->moduleManager !== null) { + return $this->moduleManager; + } + + return $this->moduleManager = new ContentContainerModuleManager(['contentContainer' => $this]); + } + /** * Returns user group for the given $user or current logged in user if no $user instance was provided. * @@ -238,7 +282,7 @@ abstract class ContentContainerActiveRecord extends ActiveRecord { return []; } - + /** * Returns weather or not the contentcontainer is archived. (Default false). * @return boolean diff --git a/protected/humhub/modules/content/components/ContentContainerController.php b/protected/humhub/modules/content/components/ContentContainerController.php index 6fea64235c..cad58913b6 100644 --- a/protected/humhub/modules/content/components/ContentContainerController.php +++ b/protected/humhub/modules/content/components/ContentContainerController.php @@ -2,108 +2,95 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2015 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ namespace humhub\modules\content\components; -use humhub\components\Controller; -use humhub\modules\space\behaviors\SpaceController; -use humhub\modules\space\models\Space; -use humhub\modules\space\widgets\Image; -use humhub\modules\user\behaviors\ProfileController; -use humhub\modules\user\models\User; use Yii; -use yii\helpers\Html; -use yii\helpers\Json; use yii\web\HttpException; +use humhub\components\Controller; +use humhub\modules\content\models\ContentContainer; +use humhub\modules\content\components\ContentContainerModule; +use humhub\modules\content\components\ContentContainerActiveRecord; +use humhub\modules\content\components\ContentContainerControllerAccess; /** - * ContainerController is the base controller for all space or user profile controllers. - * It automatically detects the Container by request parameters. - * Use [[ContentContainerActiveCreated::createUrl]] method to generate URLs. - * e.g. $this->contentContainer->createUrl(); - * Depends on the loaded the Container Type a Behavior with additional methods will be attached. - * - Space \humhub\modules\space\behaviors\SpaceController - * - User attached Behavior: \humhub\modules\user\behaviors\ProfileController - * - * @since 0.6 + * Controller is the base class of web controllers which acts in scope of a ContentContainer (e.g. Space or User). + * + * To automatically load the current contentcontainer the containers guid must be passed as GET parameter 'cguid'. + * You can create URLs in the scope of an ContentContainer by passing the contentContainer instance as 'container' or 'contentContainer' + * as parameter to the URLManager. + * + * Example: + * + * ``` + * $url = Url::to(['my/action', 'container' => $this->contentContainer'); + * ``` + * + * Based on the current ContentContainer a behavior (defined in ContentContainerActiveRecord::controllerBehavior) will be automatically + * attached to this controller instance. + + * The attached behavior will perform basic access checks, adds the container sublayout and perform other tasks + * (e.g. the space behavior will update the last visit membership attribute). + * + * @see \humhub\modules\space\behaviors\SpaceController + * @see \humhub\modules\user\behaviors\ProfileController */ class ContentContainerController extends Controller { + /** - * @var ContentContainerActiveRecord + * Specifies if a contentContainer (e.g. Space or User) is required to run this controller. + * Set this to false, if your controller should also act on global scope. + * + * @var boolean require cguid container parameter + */ + public $requireContainer = true; + + /** + * @var ContentContainerActiveRecord the content container (e.g. Space or User record) */ public $contentContainer = null; /** - * @var boolean automatic check user access permissions to this container + * Limit this controller only for usage on given contentcontainer types (e.g. Space). + * + * @since 1.3 + * @var array|null an array of valid content container classes. if null all container types (User & Space) are allowed. */ - public $autoCheckContainerAccess = true; + public $validContentContainerClasses = null; /** - * @var boolean hides containers sidebar in layout - * @since 0.11 - */ - public $hideSidebar = true; - - /** - * Automatically loads the underlying contentContainer (User/Space) by using - * the uguid/sguid request parameter - * - * @return bool - * @throws HttpException + * @inheritdoc */ public function init() { - $request = Yii::$app->request; - $spaceGuid = $request->get('sguid'); - $userGuid = $request->get('uguid'); - - if ($spaceGuid !== null) { - - $this->contentContainer = Space::findOne(['guid' => $spaceGuid]); - if ($this->contentContainer == null) { - throw new HttpException(404, Yii::t('base', 'Space not found!')); - } - - $this->attachBehavior('SpaceControllerBehavior', [ - 'class' => SpaceController::className(), - 'space' => $this->contentContainer, - ]); - $this->subLayout = "@humhub/modules/space/views/space/_layout"; - - // Handle case, if a non-logged user tries to acccess a hidden space - // Redirect to Login instead of showing error - if ($this->contentContainer->visibility == Space::VISIBILITY_NONE && Yii::$app->user->isGuest) { - - } - - } elseif ($userGuid !== null) { - - $this->contentContainer = User::findOne(['guid' => $userGuid]); - if ($this->contentContainer == null) { - throw new HttpException(404, Yii::t('base', 'User not found!')); - } - - $this->attachBehavior('ProfileControllerBehavior', [ - 'class' => ProfileController::className(), - 'user' => $this->contentContainer, - ]); - - $this->subLayout = "@humhub/modules/user/views/profile/_layout"; - - } else { - throw new HttpException(500, Yii::t('base', 'Could not determine content container!')); - } - - - if (!$this->checkModuleIsEnabled()) { - throw new HttpException(405, Yii::t('base', 'Module is not enabled on this content container!')); - } - parent::init(); + + // Load the ContentContainer + $guid = Yii::$app->request->get('cguid', Yii::$app->request->get('sguid', Yii::$app->request->get('uguid'))); + if (!empty($guid)) { + $contentContainerModel = ContentContainer::findOne(['guid' => $guid]); + if ($contentContainerModel !== null) { + $this->contentContainer = $contentContainerModel->getPolymorphicRelation(); + } + } + + if ($this->requireContainer && $this->contentContainer === null) { + throw new HttpException(404, Yii::t('base', 'Could not find requested page.')); + } + + if ($this->validContentContainerClasses !== null) { + if ($this->contentContainer === null || !in_array($this->contentContainer->className(), $this->validContentContainerClasses)) { + throw new HttpException(400); + } + } + + if ($this->contentContainer !== null && $this->contentContainer->controllerBehavior) { + $this->attachBehavior('containerControllerBehavior', ['class' => $this->contentContainer->controllerBehavior]); + } } /** @@ -111,7 +98,7 @@ class ContentContainerController extends Controller */ public function beforeAction($action) { - if (parent::beforeAction($action) === false) { + if (!parent::beforeAction($action)) { return false; } @@ -121,43 +108,11 @@ class ContentContainerController extends Controller return false; } - // Auto check access rights to this container - if ($this->contentContainer != null && $this->autoCheckContainerAccess) { - $this->checkContainerAccess(); - } - - if ($this->contentContainer instanceof Space && (Yii::$app->request->isPjax || !Yii::$app->request->isAjax)) { - $options = [ - 'guid' => $this->contentContainer->guid, - 'name' => Html::encode($this->contentContainer->name), - 'archived' => $this->contentContainer->isArchived(), - 'image' => Image::widget([ - 'space' => $this->contentContainer, - 'width' => 32, - 'htmlOptions' => [ - 'class' => 'current-space-image', - ], - ]), - ]; - - $this->view->registerJs('humhub.modules.space.setSpace(' . Json::encode($options) . ', ' . - Json::encode(Yii::$app->request->isPjax) . ')'); - } + $this->checkModuleIsEnabled(); return true; } - /** - * Checks if current user can access current ContentContainer by using - * underlying behavior ProfileControllerBehavior/SpaceControllerBehavior. - * If access check failed, an CHttpException is thrown. - */ - public function checkContainerAccess() - { - // Implemented by behavior - $this->checkAccess(); - } - /** * @inheritdoc */ @@ -167,18 +122,16 @@ class ContentContainerController extends Controller } /** - * Checks if current module is enabled on this content container. - * - * @todo Also support submodules - * @return boolean is current module enabled + * Checks if the requested module is available in this contentContainer. + * + * @throws HttpException if the module is not enabled */ - public function checkModuleIsEnabled() + protected function checkModuleIsEnabled() { - if ($this->module instanceof ContentContainerModule && $this->contentContainer !== null) { - return $this->contentContainer->isModuleEnabled($this->module->id); + if ($this->module instanceof ContentContainerModule && $this->contentContainer !== null && + !$this->contentContainer->moduleManager->isEnabled($this->module->id)) { + throw new HttpException(405, Yii::t('base', 'Module is not enabled on this content container!')); } - - return true; } } diff --git a/protected/humhub/modules/content/components/ContentContainerModule.php b/protected/humhub/modules/content/components/ContentContainerModule.php index 3d171e515e..3564426c2d 100644 --- a/protected/humhub/modules/content/components/ContentContainerModule.php +++ b/protected/humhub/modules/content/components/ContentContainerModule.php @@ -8,8 +8,8 @@ namespace humhub\modules\content\components; - use humhub\components\Module; +use humhub\modules\content\models\ContentContainerModuleState; /** * Base Module with ContentContainer support. @@ -23,6 +23,25 @@ use humhub\components\Module; class ContentContainerModule extends Module { + /** + * @inheritdoc + */ + public function disable() + { + // disable in content containers + $contentContainerQuery = ContentContainerModuleManager::getContentContainerQueryByModule($this->id); + foreach ($contentContainerQuery->all() as $contentContainer) { + /* @var $contentContainer \humhub\modules\content\models\ContentContainer */ + $this->disableContentContainer($contentContainer->getPolymorphicRelation()); + } + + foreach (ContentContainerModuleState::findAll(['module_id' => $this->id]) as $moduleState) { + $moduleState->delete(); + } + + parent::disable(); + } + /** * Returns the list of valid content container classes this module supports. * diff --git a/protected/humhub/modules/content/components/ContentContainerModuleManager.php b/protected/humhub/modules/content/components/ContentContainerModuleManager.php new file mode 100644 index 0000000000..4c6c2cd2bf --- /dev/null +++ b/protected/humhub/modules/content/components/ContentContainerModuleManager.php @@ -0,0 +1,267 @@ +canDisable($id)) { + Yii::$app->moduleManager->getModule($id)->disableContentContainer($this->contentContainer); + + $moduleState = $this->getModuleStateRecord($id); + $moduleState->module_state = ContentContainerModuleState::STATE_DISABLED; + $moduleState->save(); + + return true; + } + + return false; + } + + /** + * Enables a module for this content container + * + * @param string $id the module id + * @return boolean + */ + public function enable($id) + { + if ($this->canEnable($id)) { + Yii::$app->moduleManager->getModule($id)->enableContentContainer($this->contentContainer); + + $moduleState = $this->getModuleStateRecord($id); + $moduleState->module_state = ContentContainerModuleState::STATE_ENABLED; + $moduleState->save(); + + return true; + } + + return false; + } + + /** + * Checks whether the module is activated or not + * + * @param string $id the module id + * @return boolean + */ + public function isEnabled($id) + { + return in_array($id, $this->getEnabled()); + } + + /** + * Checks whether the module can be enabled or not + * + * @param string $id the module id + * @return boolean + */ + public function canEnable($id) + { + $available = $this->getAvailable(); + if (!$this->isEnabled($id) && array_key_exists($id, $available)) { + return true; + } + + return false; + } + + /** + * Checks whether the module can be disabled or not + * + * @param string $id the module id + * @return boolean + */ + public function canDisable($id) + { + if (!$this->isEnabled($id) || self::getDefaultState($this->contentContainer->className(), $id) === ContentContainerModuleState::STATE_FORCE_ENABLED) { + return false; + } + + return true; + } + + /** + * Returns an array of all enabled module ids + * + * @return array a list of enabled module ids + */ + public function getEnabled() + { + $enabled = []; + $available = $this->getAvailable(); + foreach ($this->getStates() as $id => $state) { + if (array_key_exists($id, $available) && ($state === ContentContainerModuleState::STATE_ENABLED || $state === ContentContainerModuleState::STATE_FORCE_ENABLED)) { + $enabled[] = $id; + } + } + + return $enabled; + } + + /** + * Returns an array of all available modules + * + * @return ContentContainerModule[] a list of modules + */ + public function getAvailable() + { + if ($this->_available !== null) { + return $this->_available; + } + + $this->_available = []; + + foreach (Yii::$app->moduleManager->getModules() as $id => $module) { + if ($module instanceof ContentContainerModule && Yii::$app->hasModule($module->id) && + $module->hasContentContainerType($this->contentContainer->className())) { + $this->_available[$module->id] = $module; + } + } + + return $this->_available; + } + + /** + * Returns an array of all module states. + * + * @see Module + * @return array a list of modules with the corresponding state + */ + protected function getStates() + { + $states = []; + + // Get states for this contentcontainer from database + foreach (ContentContainerModuleState::findAll(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]) as $module) { + $states[$module->module_id] = $module->module_state; + } + + // Get default states, when no state is stored + foreach ($this->getAvailable() as $module) { + if (!isset($states[$module->id])) { + $states[$module->id] = self::getDefaultState($this->contentContainer->className(), $module->id); + } + } + + return $states; + } + + /** + * Sets the default state for a module based on the contentcontainer class + * + * @param string $class the class name (e.g. Space or User) + * @param string $id the module id + * @param int $state the state + */ + public static function setDefaultState($class, $id, $state) + { + $reflect = new ReflectionClass($class); + Yii::$app->getModule($id)->settings->set('moduleManager.defaultState.' . $reflect->getShortName(), $state); + } + + /** + * Returns the default module state for a given contentcontainer class + * + * @param string $class the class name (e.g. Space or User) + * @param string $id the module id + * @return int|null the default state or null when no default state is defined + */ + public static function getDefaultState($class, $id) + { + $reflect = new ReflectionClass($class); + $state = Yii::$app->getModule($id)->settings->get('moduleManager.defaultState.' . $reflect->getShortName()); + + if ($state === null) { + return null; + } else { + return (int) $state; + } + } + + /** + * Returns an Module record instance for the given module id + * + * @see Module + * @param string $id the module id + * @return Module the Module record instance + */ + protected function getModuleStateRecord($id) + { + $moduleState = ContentContainerModuleState::findOne(['module_id' => $id, 'contentcontainer_id' => $this->contentContainer->contentcontainer_id]); + if ($moduleState === null) { + $moduleState = new ContentContainerModuleState; + $moduleState->contentcontainer_id = $this->contentContainer->contentcontainer_id; + $moduleState->module_id = $id; + } + + return $moduleState; + } + + /** + * Returns a query for \humhub\modules\content\models\ContentContainer where the given module is enabled. + * + * @param string $id the module mid + * @return \yii\db\ActiveQuerythe list of content container + */ + public static function getContentContainerQueryByModule($id) + { + $query = ContentContainer::find(); + + $query->leftJoin('contentcontainer_module', 'contentcontainer_module.contentcontainer_id=contentcontainer.id AND contentcontainer_module.module_id=:moduleId', [':moduleId' => $id]); + $query->andWhere(['contentcontainer_module.module_state' => ContentContainerModuleState::STATE_ENABLED]); + $query->orWhere(['contentcontainer_module.module_state' => ContentContainerModuleState::STATE_FORCE_ENABLED]); + + $moduleSettings = Yii::$app->getModule($id)->settings; + + // Add default enabled modules + $contentContainerClasses = [\humhub\modules\user\models\User::class, \humhub\modules\space\models\Space::class]; + foreach ($contentContainerClasses as $class) { + $reflect = new ReflectionClass($class); + $defaultState = (int) $moduleSettings->get('moduleManager.defaultState.' . $reflect->getShortName()); + if ($defaultState === ContentContainerModuleState::STATE_ENABLED || $defaultState === ContentContainerModuleState::STATE_FORCE_ENABLED) { + $query->orWhere(['contentcontainer.class' => $class]); + } + } + + return $query; + } + +} diff --git a/protected/humhub/modules/content/components/behaviors/CompatModuleManager.php b/protected/humhub/modules/content/components/behaviors/CompatModuleManager.php new file mode 100644 index 0000000000..aff378ef43 --- /dev/null +++ b/protected/humhub/modules/content/components/behaviors/CompatModuleManager.php @@ -0,0 +1,65 @@ +moduleManager = $this->owner->moduleManager; + } + + public function getAvailableModules() + { + return $this->moduleManager->getAvailable(); + } + + public function getEnabledModules() + { + return $this->moduleManager->getEnabled(); + } + + public function isModuleEnabled($moduleId) + { + return $this->moduleManager->isEnabled($moduleId); + } + + public function enableModule($moduleId) + { + return $this->moduleManager->enable($moduleId); + } + + public function canDisableModule($id) + { + return $this->moduleManager->canDisable($id); + } + + public function disableModule($moduleId) + { + return $this->moduleManager->disable($moduleId); + } + +} diff --git a/protected/humhub/modules/content/config.php b/protected/humhub/modules/content/config.php index eace53063e..12914d1caf 100644 --- a/protected/humhub/modules/content/config.php +++ b/protected/humhub/modules/content/config.php @@ -17,6 +17,7 @@ return [ ['class' => IntegrityController::className(), 'event' => IntegrityController::EVENT_ON_RUN, 'callback' => array(Events::className(), 'onIntegrityCheck')], ['class' => WallEntryAddons::className(), 'event' => WallEntryAddons::EVENT_INIT, 'callback' => array(Events::className(), 'onWallEntryAddonInit')], ['class' => User::className(), 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::className(), 'onUserDelete']], + ['class' => User::className(), 'event' => User::EVENT_BEFORE_SOFT_DELETE, 'callback' => [Events::className(), 'onUserSoftDelete']], ['class' => Space::className(), 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::className(), 'onSpaceDelete']], ['class' => Search::className(), 'event' => Search::EVENT_ON_REBUILD, 'callback' => [Events::className(), 'onSearchRebuild']], ['class' => ContentActiveRecord::className(), 'event' => ContentActiveRecord::EVENT_AFTER_INSERT, 'callback' => [Events::className(), 'onContentActiveRecordSave']], diff --git a/protected/humhub/modules/content/migrations/m150927_190830_create_contentcontainer.php b/protected/humhub/modules/content/migrations/m150927_190830_create_contentcontainer.php index fe641d9df4..8ae832a83c 100644 --- a/protected/humhub/modules/content/migrations/m150927_190830_create_contentcontainer.php +++ b/protected/humhub/modules/content/migrations/m150927_190830_create_contentcontainer.php @@ -16,6 +16,12 @@ class m150927_190830_create_contentcontainer extends Migration 'owner_user_id' => Schema::TYPE_INTEGER, 'wall_id' => Schema::TYPE_INTEGER, ), ''); + + + # 1.3 - prepare utf8_mb4 support + $this->alterColumn('contentcontainer', 'guid', 'char(36) NOT NULL'); + $this->alterColumn('contentcontainer', 'class', 'char(60) NOT NULL'); + $this->createIndex('unique_target', 'contentcontainer', ['class', 'pk'], true); $this->createIndex('unique_guid', 'contentcontainer', ['guid'], true); diff --git a/protected/humhub/modules/content/models/ContentContainerModuleState.php b/protected/humhub/modules/content/models/ContentContainerModuleState.php new file mode 100644 index 0000000000..27b4154ffd --- /dev/null +++ b/protected/humhub/modules/content/models/ContentContainerModuleState.php @@ -0,0 +1,33 @@ + 'State', ]; } + } diff --git a/protected/humhub/modules/file/Events.php b/protected/humhub/modules/file/Events.php index 1da447d064..c547acf046 100644 --- a/protected/humhub/modules/file/Events.php +++ b/protected/humhub/modules/file/Events.php @@ -12,6 +12,7 @@ use humhub\modules\search\engine\Search; use humhub\modules\file\models\File; use yii\base\Event; use humhub\modules\search\events\SearchAttributesEvent; +use humhub\modules\file\converter\TextConverter; /** * Events provides callbacks to handle events. @@ -113,8 +114,16 @@ class Events extends \yii\base\Object foreach (File::findAll(['object_model' => $event->record->className(), 'object_id' => $event->record->id]) as $file) { /* @var $file File */ + + $textContent = null; + $textConverter = new TextConverter(); + if ($textConverter->applyFile($file)) { + $textContent = $textConverter->getContentAsText(); + } + $event->attributes['files'][$file->id] = [ - 'name' => $file->file_name + 'name' => $file->file_name, + 'content' => $textContent ]; // Add comment related attributes diff --git a/protected/humhub/modules/file/converter/TextConverter.php b/protected/humhub/modules/file/converter/TextConverter.php new file mode 100644 index 0000000000..b67d42eb85 --- /dev/null +++ b/protected/humhub/modules/file/converter/TextConverter.php @@ -0,0 +1,141 @@ + '/usr/bin/pdftotext -q -enc UTF-8 {fileName} {outputFileName}', + * 'only' => ['pdf'] + * ], + * [ + * 'cmd' => '/usr/bin/java -jar /path/to/tika-app-1.16.jar --text {fileName} 2>/dev/null', + * 'except' => ['image/'] + * ] + * ``` + * + * @var array + */ + public $converter = []; + + /** + * @inheritdoc + */ + protected function canConvert(File $file) + { + $originalFile = $file->store->get(); + + if (!is_file($originalFile)) { + return false; + } + + if ($this->getConverter() === null) { + // No text converter found for given file + return false; + } + + return true; + } + + /** + * @inheritdoc + */ + protected function convert($fileName) + { + $convertedFile = $this->file->store->get($fileName); + + if (is_file($convertedFile)) { + return; + } + + $textContent = ''; + $converter = $this->getConverter(); + + if ($converter !== null) { + if (Yii::$app->request->isConsoleRequest) { + print "C"; + } + + $command = str_replace('{fileName}', $this->file->store->get(), $converter['cmd']); + if (strpos($command, "{outputFileName}") !== false) { + $command = str_replace('{outputFileName}', $convertedFile, $command); + shell_exec($command); + } else { + $textContent = shell_exec($command) . "\n"; + file_put_contents($convertedFile, $textContent); + } + } + } + + /** + * Returns the first matching converter for the file + * + * @return array the converter definitions + */ + public function getConverter() + { + foreach ($this->converter as $converter) { + // Check Exceptions + if (!empty($converter['except']) && is_array($converter['except'])) { + foreach ($converter['except'] as $except) { + if (strpos($this->file->mime_type, $except) !== false || FileHelper::getExtension($this->file) == $except) { + continue 2; + } + } + } + + if (!empty($converter['only']) && is_array($converter['only'])) { + foreach ($converter['only'] as $only) { + if (strpos($this->file->mime_type, $only) !== false || FileHelper::getExtension($this->file) == $only) { + return $converter; + } + } + } else { + // Valid for all file types + return $converter; + } + } + + return null; + } + + /** + * Returns the file content as text + * + * @return string the text output + */ + public function getContentAsText() + { + $fileName = $this->getFilename(); + $convertedFile = $this->file->store->get($fileName); + + if (is_file($convertedFile)) { + return file_get_contents($convertedFile); + } + return null; + } + +} diff --git a/protected/humhub/modules/file/widgets/FilePreview.php b/protected/humhub/modules/file/widgets/FilePreview.php index d5bfb59e59..c4cf65ba4c 100644 --- a/protected/humhub/modules/file/widgets/FilePreview.php +++ b/protected/humhub/modules/file/widgets/FilePreview.php @@ -2,9 +2,15 @@ namespace humhub\modules\file\widgets; -use humhub\modules\file\libs\FileHelper; -use humhub\widgets\JsWidget; +use Yii; +use yii\helpers\ArrayHelper; use yii\helpers\Html; +use humhub\widgets\JsWidget; +use humhub\modules\file\models\File; +use humhub\modules\file\libs\FileHelper; +use humhub\modules\search\libs\SearchHelper; +use humhub\modules\search\controllers\SearchController; +use humhub\modules\file\converter\TextConverter; /** * @@ -14,13 +20,19 @@ use yii\helpers\Html; class FilePreview extends JsWidget { + /** + * @inheritdoc + */ public $jsWidget = "file.Preview"; public $items; public $model; public $hideImageFileInfo = false; public $edit = false; + + /** + * @inheritdoc + */ public $visible = false; - public $preventPopover = false; public $popoverPosition = 'right'; @@ -58,12 +70,12 @@ class FilePreview extends JsWidget protected function getFileData() { $files = $this->getFiles(); - + $result = []; foreach ($files as $file) { - if($file) { - $result[] = FileHelper::getFileInfos($file); + if ($file) { + $result[] = ArrayHelper::merge(FileHelper::getFileInfos($file), ['highlight' => $this->isHighlighed($file)]); } } @@ -76,14 +88,36 @@ class FilePreview extends JsWidget return []; } - if($this->items) { + if ($this->items) { return $this->items; } - if($this->showInStream === null) { + if ($this->showInStream === null) { return $this->model->fileManager->findAll(); } else { return $this->model->fileManager->findStreamFiles($this->showInStream); } } + + /** + * Checks whether the file should be highlighed in the results or not. + * + * @param File $file + * @return boolean is highlighed + */ + protected function isHighlighed(File $file) + { + if (Yii::$app->controller instanceof SearchController) { + if (SearchController::$keyword !== null) { + $converter = new TextConverter(); + if ($converter->applyFile($file) && + SearchHelper::matchQuery(SearchController::$keyword, $converter->getContentAsText())) { + return true; + } + } + } + + return false; + } + } diff --git a/protected/humhub/modules/live/Events.php b/protected/humhub/modules/live/Events.php index 969a2d1526..ee0ab8fcda 100644 --- a/protected/humhub/modules/live/Events.php +++ b/protected/humhub/modules/live/Events.php @@ -38,7 +38,7 @@ class Events extends \yii\base\Object */ public static function onMemberEvent(MemberEvent $event) { - Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user->id); + Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user); } /** @@ -47,8 +47,8 @@ class Events extends \yii\base\Object */ public static function onFriendshipEvent(FriendshipEvent $event) { - Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user1->id); - Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user2->id); + Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user1); + Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user2); } /** @@ -58,7 +58,7 @@ class Events extends \yii\base\Object public static function onFollowEvent(FollowEvent $event) { if ($event->target instanceof ContentContainerActiveRecord) { - Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user->id); + Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user); } } diff --git a/protected/humhub/modules/live/Module.php b/protected/humhub/modules/live/Module.php index fbf4988610..77c9f22f42 100644 --- a/protected/humhub/modules/live/Module.php +++ b/protected/humhub/modules/live/Module.php @@ -22,36 +22,11 @@ use humhub\modules\friendship\models\Friendship; class Module extends \humhub\components\Module { - /** - * Defines the minimum polling interval in seconds if the default polling client is active. - */ - public $minPollInterval = 15; - - /** - * Defines the maximum polling interval in seconds if the default polling client is active. - */ - public $maxPollInterval = 45; - - /** - * Factor used in the actual interval calculation in case of user idle. - */ - public $idleFactor = 0.1; - - /** - * Interval for updating the update delay in case of user idle in seconds. - */ - public $idleInterval = 20; - /** * @inheritdoc */ public $isCoreModule = true; - /** - * @var int seconds to delete old live events - */ - public $maxLiveEventAge = 600; - /** * @var string cache prefix for legitimate content container ids by user */ @@ -81,6 +56,12 @@ class Module extends \humhub\components\Module Content::VISIBILITY_OWNER => [], ]; + // When no content container record exists (yet) + // This may happen during the registration process + if ($user->contentContainerRecord === null) { + return $legitimation; + } + // Add users own content container (user == contentcontainer) $legitimation[Content::VISIBILITY_OWNER][] = $user->contentContainerRecord->id; @@ -103,9 +84,16 @@ class Module extends \humhub\components\Module } Yii::$app->cache->set(self::$legitimateCachePrefix . $user->id, $legitimation); + Yii::$app->live->driver->onContentContainerLegitimationChanged($user, $legitimation); }; return $legitimation; } + public function refreshLegitimateContentContainerIds(User $user) + { + Yii::$app->cache->delete(self::$legitimateCachePrefix . $user->id); + $this->getLegitimateContentContainerIds($user); + } + } diff --git a/protected/humhub/modules/live/assets/LiveAsset.php b/protected/humhub/modules/live/assets/LiveAsset.php index 7b8feaffda..12758d3817 100644 --- a/protected/humhub/modules/live/assets/LiveAsset.php +++ b/protected/humhub/modules/live/assets/LiveAsset.php @@ -16,6 +16,11 @@ class LiveAsset extends AssetBundle public $css = []; public $js = [ 'js/humhub.live.js', - 'js/humhub.live.poll.js' + 'js/humhub.live.poll.js', + 'js/humhub.live.push.js', + ]; + + public $publishOptions = [ + 'forceCopy' => false, ]; } diff --git a/protected/humhub/modules/live/controllers/PollController.php b/protected/humhub/modules/live/controllers/PollController.php index 8d429eb238..2fa9e620b2 100644 --- a/protected/humhub/modules/live/controllers/PollController.php +++ b/protected/humhub/modules/live/controllers/PollController.php @@ -54,7 +54,7 @@ class PollController extends Controller } if (parent::beforeAction($action)) { - if (!Yii::$app->live->driver instanceof \humhub\modules\live\driver\Database) { + if (!Yii::$app->live->driver instanceof \humhub\modules\live\driver\Poll) { throw new Exception('Polling is only available when using the live database driver!'); } diff --git a/protected/humhub/modules/live/driver/BaseDriver.php b/protected/humhub/modules/live/driver/BaseDriver.php index 50e12f6790..bf43085571 100644 --- a/protected/humhub/modules/live/driver/BaseDriver.php +++ b/protected/humhub/modules/live/driver/BaseDriver.php @@ -10,6 +10,7 @@ namespace humhub\modules\live\driver; use yii\base\Object; use humhub\modules\live\components\LiveEvent; +use humhub\modules\user\models\User; /** * Base driver for live event storage and distribution @@ -27,4 +28,26 @@ abstract class BaseDriver extends Object * @return boolean indicates the sent was successful */ abstract public function send(LiveEvent $liveEvent); + + /** + * Returns the JavaScript Configuration for this driver + * + * @since 1.3 + * @see \humhub\widgets\CoreJsConfig + * @return array the JS Configuratoin + */ + abstract public function getJsConfig(); + + /** + * This callback will be executed whenever the access rules for a + * contentcontainer is changed. e.g. user joined a new space as member. + * + * @since 1.3 + * @see \humhub\modules\live\Module::getLegitimateContentContainerIds() + */ + public function onContentContainerLegitimationChanged(User $user, $legitimation = []) + { + + } + } diff --git a/protected/humhub/modules/live/driver/Database.php b/protected/humhub/modules/live/driver/Database.php deleted file mode 100644 index 9c690c47ce..0000000000 --- a/protected/humhub/modules/live/driver/Database.php +++ /dev/null @@ -1,38 +0,0 @@ -serialized_data = serialize($liveEvent); - $model->created_at = time(); - $model->visibility = $liveEvent->visibility; - $model->contentcontainer_id = $liveEvent->contentContainerId; - $model->created_at = time(); - return $model->save(); - } - -} diff --git a/protected/humhub/modules/live/driver/Poll.php b/protected/humhub/modules/live/driver/Poll.php new file mode 100644 index 0000000000..7b4e5183dc --- /dev/null +++ b/protected/humhub/modules/live/driver/Poll.php @@ -0,0 +1,84 @@ +serialized_data = serialize($liveEvent); + $model->created_at = time(); + $model->visibility = $liveEvent->visibility; + $model->contentcontainer_id = $liveEvent->contentContainerId; + $model->created_at = time(); + return $model->save(); + } + + /** + * @inheritdoc + */ + public function getJsConfig() + { + return [ + 'type' => 'humhub.modules.live.poll.PollClient', + 'options' => [ + 'url' => Url::to(['/live/poll']), + 'initTime' => time(), + 'minInterval' => $this->minPollInterval, // Minimal polling request interval in seconds. + 'maxInterval' => $this->maxPollInterval, // Maximal polling request interval in seconds. + 'idleFactor' => $this->idleFactor, // Factor used in the actual interval calculation in case of user idle. + 'idleInterval' => $this->idleInterval // Interval for updating the update delay in case of user idle in seconds. + ] + ]; + } + +} diff --git a/protected/humhub/modules/live/driver/Push.php b/protected/humhub/modules/live/driver/Push.php new file mode 100644 index 0000000000..8c0a529670 --- /dev/null +++ b/protected/humhub/modules/live/driver/Push.php @@ -0,0 +1,124 @@ +redis = Instance::ensure($this->redis, Connection::className()); + + if (empty($this->jwtKey)) { + throw new InvalidConfigException('Push driver JWT key is not specified.'); + } + } + + /** + * @inheritdoc + */ + public function send(LiveEvent $liveEvent) + { + $this->redis->publish($this->pushChannel, Json::encode($liveEvent->getData())); + } + + /** + * @inheritdoc + */ + public function getJsConfig() + { + return [ + 'type' => 'humhub.modules.live.push.PushClient', + 'options' => [ + 'url' => $this->pushServiceUrl, + 'jwt' => $this->generateJwtAuthorization(), + ] + ]; + } + + /** + * Generates an JWT authorization of the current user including + * the contentContainer id legitmation. + * + * @return string the JWT string + */ + public function generateJwtAuthorization() + { + if (Yii::$app->user->isGuest) { + return ''; + } + + $user = Yii::$app->user->getIdentity(); + $token = [ + 'iss' => Url::to(['/'], true), + 'sub' => Yii::$app->user->id, + 'legitmation' => Yii::$app->getModule('live')->getLegitimateContentContainerIds($user) + ]; + return JWT::encode($token, $this->jwtKey); + } + + /** + * @inheritdoc + */ + public function onContentContainerLegitimationChanged(User $user, $legitimation = []) + { + $this->send(new LegitimationChanged(['contentContainerId' => $user->contentcontainer_id, 'userId' => $user->id, 'legitimation' => $legitimation])); + } + +} diff --git a/protected/humhub/modules/live/jobs/DatabaseCleanup.php b/protected/humhub/modules/live/jobs/DatabaseCleanup.php index 6649c6596b..6b04b2772c 100644 --- a/protected/humhub/modules/live/jobs/DatabaseCleanup.php +++ b/protected/humhub/modules/live/jobs/DatabaseCleanup.php @@ -10,7 +10,8 @@ namespace humhub\modules\live\jobs; use Yii; use humhub\modules\live\models\Live; -use humhub\components\queue\ActiveJob; +use humhub\modules\queue\ActiveJob; +use humhub\modules\live\driver\Poll; /** * DatabaseCleanup removes old live events @@ -26,7 +27,9 @@ class DatabaseCleanup extends ActiveJob */ public function run() { - Live::deleteAll('created_at +' . Yii::$app->getModule('live')->maxLiveEventAge . ' < ' . time()); + if (Yii::$app->live->driver instanceof Poll) { + Live::deleteAll('created_at +' . Yii::$app->live->maxLiveEventAge . ' < ' . time()); + } } } diff --git a/protected/humhub/modules/live/live/LegitimationChanged.php b/protected/humhub/modules/live/live/LegitimationChanged.php new file mode 100644 index 0000000000..5e1fdb1de4 --- /dev/null +++ b/protected/humhub/modules/live/live/LegitimationChanged.php @@ -0,0 +1,42 @@ +visibility = Content::VISIBILITY_OWNER; + } + +} diff --git a/protected/humhub/modules/live/resources/js/humhub.live.push.js b/protected/humhub/modules/live/resources/js/humhub.live.push.js new file mode 100644 index 0000000000..f9d82e8d73 --- /dev/null +++ b/protected/humhub/modules/live/resources/js/humhub.live.push.js @@ -0,0 +1,42 @@ +humhub.module('live.push', function (module, require, $) { + var client = require('client'); + var event = require('event'); + var object = require('util').object; + + var PushClient = function (options) { + if (!options) { + module.log.error('Could not initialize PushClient. No options given!'); + return; + } + this.options = options; + this.init(); + }; + + PushClient.prototype.init = function () { + if (!this.options.url) { + module.log.error('Could not initialize PushClient. No url option given!'); + return; + } + + var that = this; + var socket = io.connect(this.options.url); + socket.on('connect', function () { + socket.emit('authenticate', {token: that.options.jwt}); + }); + socket.on('error', function (err) { + module.log.error(err); + }); + socket.on('message', function (data) { + message = JSON.parse(data); + event.trigger(message.type.replace(/\./g, ':'), [[message]]); + }); + }; + + var _handleUpdateError = function (e) { + module.log.error(e); + }; + + module.export({ + PushClient: PushClient + }); +}); \ No newline at end of file diff --git a/protected/humhub/modules/notification/jobs/SendBulkNotification.php b/protected/humhub/modules/notification/jobs/SendBulkNotification.php index 434b53477d..23ce9d95f3 100644 --- a/protected/humhub/modules/notification/jobs/SendBulkNotification.php +++ b/protected/humhub/modules/notification/jobs/SendBulkNotification.php @@ -8,7 +8,7 @@ namespace humhub\modules\notification\jobs; use Yii; -use humhub\components\queue\ActiveJob; +use humhub\modules\queue\ActiveJob; /** * Description of SendNotification @@ -22,17 +22,17 @@ class SendBulkNotification extends ActiveJob * @var array Basenotification data as array. */ public $notification; - + /** * @var integer[] Recepient userids. */ public $recepients; - + /** * @inheritdoc */ public function run() - { - Yii::$app->notification->sendBulk($this->notification, $this->recepients); + { + Yii::$app->notification->sendBulk($this->notification, $this->recepients); } } diff --git a/protected/humhub/modules/notification/jobs/SendNotification.php b/protected/humhub/modules/notification/jobs/SendNotification.php index 9e28ca8d06..afa5221bf3 100644 --- a/protected/humhub/modules/notification/jobs/SendNotification.php +++ b/protected/humhub/modules/notification/jobs/SendNotification.php @@ -9,7 +9,7 @@ namespace humhub\modules\notification\jobs; use Yii; -use humhub\components\queue\ActiveJob; +use humhub\modules\queue\ActiveJob; /** * Description of SendNotification @@ -23,17 +23,17 @@ class SendNotification extends ActiveJob * @var humhub\modules\notification\components\BaseNotification notification instance */ public $notification; - + /** * @var \humhub\modules\user\models\User Recepient user id. */ public $recepient; - + /** * @inheritdoc */ public function run() { - Yii::$app->notification->send($this->notification, $this->recepient); + Yii::$app->notification->send($this->notification, $this->recepient); } } diff --git a/protected/humhub/modules/queue/ActiveJob.php b/protected/humhub/modules/queue/ActiveJob.php new file mode 100644 index 0000000000..f6ad36f2d5 --- /dev/null +++ b/protected/humhub/modules/queue/ActiveJob.php @@ -0,0 +1,36 @@ +run(); + } + +} diff --git a/protected/humhub/modules/queue/Events.php b/protected/humhub/modules/queue/Events.php new file mode 100644 index 0000000000..12016804ac --- /dev/null +++ b/protected/humhub/modules/queue/Events.php @@ -0,0 +1,80 @@ +queue->push(new CleanupExclusiveJobs()); + } + + /** + * Callback for errors while queue execution + * + * @param ErrorEvent $event + */ + public static function onQueueError(ErrorEvent $event) + { + /* @var $exception \Expection */ + $exception = $event->error; + Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue'); + } + + /** + * Callback before new jobs in the queue. + * Handles exclusive jobs. + * + * @param PushEvent $event + */ + public static function onQueueBeforePush(PushEvent $event) + { + if ($event->job instanceof ExclusiveJobInterface) { + // Do not add exclusive jobs if already exists in queue + if (QueueHelper::isQueued($event->job)) { + $event->handled = true; + } + } + } + + /** + * Callback after new jobs in the queue. + * Handles exclusive jobs. + * + * @param PushEvent $event + */ + public static function onQueueAfterPush(PushEvent $event) + { + if ($event->job instanceof ExclusiveJobInterface) { + QueueHelper::markAsQueued($event->id, $event->job); + } + } + +} diff --git a/protected/humhub/modules/queue/Module.php b/protected/humhub/modules/queue/Module.php new file mode 100644 index 0000000000..b2cc28ccf2 --- /dev/null +++ b/protected/humhub/modules/queue/Module.php @@ -0,0 +1,22 @@ + + * @since 1.3 + */ +class Module extends BaseModule +{ + +} diff --git a/protected/humhub/modules/queue/config.php b/protected/humhub/modules/queue/config.php new file mode 100644 index 0000000000..ac2aeda2eb --- /dev/null +++ b/protected/humhub/modules/queue/config.php @@ -0,0 +1,23 @@ + 'queue', + 'class' => Module::class, + 'isCoreModule' => true, + 'events' => [ + [CronController::class, CronController::EVENT_ON_DAILY_RUN, [Events::class, 'onCronRun']], + [Queue::class, Queue::EVENT_AFTER_ERROR, [Events::class, 'onQueueError']], + [Queue::class, Queue::EVENT_BEFORE_PUSH, [Events::class, 'onQueueBeforePush']], + [Queue::class, Queue::EVENT_AFTER_PUSH, [Events::class, 'onQueueAfterPush']] + ], +]; diff --git a/protected/humhub/modules/queue/driver/Instant.php b/protected/humhub/modules/queue/driver/Instant.php new file mode 100644 index 0000000000..3e9dbe3596 --- /dev/null +++ b/protected/humhub/modules/queue/driver/Instant.php @@ -0,0 +1,44 @@ +handleMessage($this->messageId, $message, $ttr); + $this->messageId++; + } + + /** + * @inheritdoc + */ + protected function status($id) + { + return Queue::STATUS_DONE; + } + +} diff --git a/protected/humhub/modules/queue/driver/MySQL.php b/protected/humhub/modules/queue/driver/MySQL.php new file mode 100644 index 0000000000..5e6fb68b2a --- /dev/null +++ b/protected/humhub/modules/queue/driver/MySQL.php @@ -0,0 +1,27 @@ + $job->getExclusiveJobId()]); + if ($queueExclusive === null || $queueExclusive->job_status == Queue::STATUS_DONE) { + return false; + } + + $jobInQueue = true; + try { + if (Yii::$app->queue->isDone($queueExclusive->job_message_id)) { + $jobInQueue = false; + } + } catch (InvalidParamException $ex) { + // not exists + $jobInQueue = false; + } + + if (!$jobInQueue) { + $queueExclusive->delete(); + return false; + } + + return true; + } + + public static function markAsQueued($jobQueueId, ExclusiveJobInterface $job) + { + $queueExclusive = QueueExclusive::findOne(['id' => $job->getExclusiveJobId()]); + if ($queueExclusive === null) { + $queueExclusive = new QueueExclusive(); + $queueExclusive->id = $job->getExclusiveJobId(); + } + $queueExclusive->job_message_id = $jobQueueId; + $queueExclusive->save(); + } + +} diff --git a/protected/humhub/modules/queue/interfaces/ExclusiveJobInterface.php b/protected/humhub/modules/queue/interfaces/ExclusiveJobInterface.php new file mode 100644 index 0000000000..440e14a97f --- /dev/null +++ b/protected/humhub/modules/queue/interfaces/ExclusiveJobInterface.php @@ -0,0 +1,22 @@ +dropTable($this->tableName); + $this->createTable($this->tableName, [ + 'id' => $this->primaryKey(), + 'channel' => $this->string()->notNull(), + 'job' => $this->binary()->notNull(), + 'pushed_at' => $this->integer()->notNull(), + 'ttr' => $this->integer()->notNull(), + 'delay' => $this->integer()->notNull(), + 'reserved_at' => $this->integer(), + 'attempt' => $this->integer(), + 'done_at' => $this->integer(), + ]); + $this->createIndex('channel', $this->tableName, 'channel'); + $this->createIndex('reserved_at', $this->tableName, 'reserved_at'); + + $this->addColumn($this->tableName, 'priority', $this->integer()->unsigned()->notNull()->defaultValue(1024)->after('delay')); + $this->createIndex('priority', $this->tableName, 'priority'); + } + + public function safeDown() + { + echo "m171025_142030_queue_update cannot be reverted.\n"; + + return false; + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m171025_142030_queue_update cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/protected/humhub/modules/queue/migrations/m171027_220519_exclusive_jobs.php b/protected/humhub/modules/queue/migrations/m171027_220519_exclusive_jobs.php new file mode 100644 index 0000000000..2cad2ba044 --- /dev/null +++ b/protected/humhub/modules/queue/migrations/m171027_220519_exclusive_jobs.php @@ -0,0 +1,39 @@ +createTable('{{queue_exclusive}}', [ + 'id' => $this->string(50)->notNull(), + 'job_message_id' => $this->string(50), + 'job_status' => $this->smallInteger()->defaultValue(2), + 'last_update' => $this->timestamp() + ]); + $this->addPrimaryKey('pk_queue_exclusive', '{{queue_exclusive}}', 'id'); + } + + public function safeDown() + { + echo "m171027_220519_exclusive_jobs cannot be reverted.\n"; + + return false; + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m171027_220519_exclusive_jobs cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/protected/humhub/modules/queue/models/QueueExclusive.php b/protected/humhub/modules/queue/models/QueueExclusive.php new file mode 100644 index 0000000000..5e3967d9db --- /dev/null +++ b/protected/humhub/modules/queue/models/QueueExclusive.php @@ -0,0 +1,30 @@ +queue->push($job); + } + /** * Search the index * diff --git a/protected/humhub/modules/search/jobs/RebuildIndex.php b/protected/humhub/modules/search/jobs/RebuildIndex.php new file mode 100644 index 0000000000..f6c80571b8 --- /dev/null +++ b/protected/humhub/modules/search/jobs/RebuildIndex.php @@ -0,0 +1,40 @@ +search->rebuild(); + } + +} diff --git a/protected/humhub/modules/space/Events.php b/protected/humhub/modules/space/Events.php index b0371de71f..2c12edaa06 100644 --- a/protected/humhub/modules/space/Events.php +++ b/protected/humhub/modules/space/Events.php @@ -2,16 +2,18 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2015 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ namespace humhub\modules\space; use Yii; +use yii\web\HttpException; +use humhub\modules\user\events\UserEvent; use humhub\modules\space\models\Space; use humhub\modules\space\models\Membership; -use yii\web\HttpException; +use humhub\modules\space\helpers\MembershipHelper; /** * Events provides callbacks for all defined module events. @@ -34,35 +36,30 @@ class Events extends \yii\base\Object } /** - * On User delete, also delete his space related stuff - * - * @param type $event + * Callback on user soft deletion + * + * @param UserEvent $event */ - public static function onUserDelete($event) + public static function onUserSoftDelete(UserEvent $event) { + $user = $event->user; - $user = $event->sender; - - // Check if the user owns some spaces - foreach (Membership::GetUserSpaces($user->id) as $space) { - if ($space->isSpaceOwner($user->id)) { - throw new HttpException(500, Yii::t('SpaceModule.base', 'Could not delete user who is a space owner! Name of Space: {spaceName}', array('spaceName' => $space->name))); - } + // Delete spaces which this user owns + foreach (MembershipHelper::getOwnSpaces($user) as $ownedSpace) { + $ownedSpace->delete(); } // Cancel all space memberships - foreach (Membership::findAll(array('user_id' => $user->id)) as $membership) { + foreach (Membership::findAll(['user_id' => $user->id]) as $membership) { // Avoid activities $membership->delete(); } // Cancel all space invites by the user - foreach (Membership::findAll(array('originator_user_id' => $user->id, 'status' => Membership::STATUS_INVITED)) as $membership) { + foreach (Membership::findAll(['originator_user_id' => $user->id, 'status' => Membership::STATUS_INVITED]) as $membership) { // Avoid activities $membership->delete(); } - - return true; } public static function onConsoleApplicationInit($event) @@ -91,15 +88,6 @@ class Events extends \yii\base\Object } } - $integrityController->showTestHeadline("Space Module - Module (" . models\Module::find()->count() . " entries)"); - foreach (models\Module::find()->joinWith('space')->each() as $module) { - if ($module->space == null) { - if ($integrityController->showFix("Deleting space module " . $module->id . " without existing space!")) { - $module->delete(); - } - } - } - $integrityController->showTestHeadline("Space Module - Memberships (" . models\Membership::find()->count() . " entries)"); foreach (models\Membership::find()->joinWith('space')->each() as $membership) { if ($membership->space == null) { diff --git a/protected/humhub/modules/space/behaviors/SpaceController.php b/protected/humhub/modules/space/behaviors/SpaceController.php index 4f9a0e895c..a19534bc24 100644 --- a/protected/humhub/modules/space/behaviors/SpaceController.php +++ b/protected/humhub/modules/space/behaviors/SpaceController.php @@ -1,44 +1,56 @@ owner->contentContainer instanceof Space) { + throw new \yii\base\InvalidValueException('Invalid contentcontainer type of controller.'); + } + + $this->space = $this->owner->contentContainer; + } + + /** + * @inheritdoc + */ public function events() { return [ @@ -46,62 +58,59 @@ class SpaceController extends Behavior ]; } - /** - * Returns the current selected space by parameter guid - * - * If space doesnt exists or there a no permissions and exception - * will thrown. - * - * @return Space - * @throws HttpException - */ - public function getSpace() + public function beforeAction($action) { - if ($this->space != null) { - return $this->space; - } + $this->updateLastVisit(); - // Get Space GUID by parameter - $guid = Yii::$app->request->get('sguid'); - - // Try Load the space - $this->space = Space::findOne([ - 'guid' => $guid - ]); - if ($this->space == null) - throw new HttpException(404, Yii::t('SpaceModule.behaviors_SpaceControllerBehavior', 'Space not found!')); - - $this->checkAccess(); - return $this->space; - } - - public function checkAccess() - { if (Yii::$app->getModule('user')->settings->get('auth.allowGuestAccess') && Yii::$app->user->isGuest && $this->space->visibility != Space::VISIBILITY_ALL) { throw new HttpException(401, Yii::t('SpaceModule.behaviors_SpaceControllerBehavior', 'You need to login to view contents of this space!')); } - // Save users last action on this space - $membership = $this->space->getMembership(Yii::$app->user->id); - if ($membership != null) { - $membership->updateLastVisit(); - } else { + if ($this->getMembership() === null && $this->space->visibility == Space::VISIBILITY_NONE && !Yii::$app->user->isAdmin()) { + throw new HttpException(404, Yii::t('SpaceModule.behaviors_SpaceControllerBehavior', 'Space is invisible!')); + } - // Super Admin can always enter - if (! Yii::$app->user->isAdmin()) { - // Space invisible? - if ($this->space->visibility == Space::VISIBILITY_NONE) { - // Not Space Member - throw new HttpException(404, Yii::t('SpaceModule.behaviors_SpaceControllerBehavior', 'Space is invisible!')); - } - } + $this->owner->subLayout = "@humhub/modules/space/views/space/_layout"; + $this->owner->prependPageTitle($this->space->name); + + if (Yii::$app->request->isPjax || !Yii::$app->request->isAjax) { + $options = [ + 'guid' => $this->owner->contentContainer->guid, + 'name' => Html::encode($this->owner->contentContainer->name), + 'archived' => $this->space->isArchived(), + 'image' => Image::widget([ + 'space' => $this->owner->contentContainer, + 'width' => 32, + 'htmlOptions' => [ + 'class' => 'current-space-image', + ], + ]), + ]; + + $this->owner->view->registerJs('humhub.modules.space.setSpace(' . Json::encode($options) . ', ' . + Json::encode(Yii::$app->request->isPjax) . ')'); } } - public function beforeAction($action) + protected function updateLastVisit() { - $this->owner->prependPageTitle($this->space->name); + $membership = $this->getMembership(); + if ($membership != null) { + $membership->updateLastVisit(); + } } + + protected function getMembership() + { + // ToDo: Cache + return $this->space->getMembership(Yii::$app->user->id); + } + + public function getSpace() + { + return $this->space; + } + } ?> diff --git a/protected/humhub/modules/space/behaviors/SpaceModelMembership.php b/protected/humhub/modules/space/behaviors/SpaceModelMembership.php index 05434b1d2b..a245e4cda8 100644 --- a/protected/humhub/modules/space/behaviors/SpaceModelMembership.php +++ b/protected/humhub/modules/space/behaviors/SpaceModelMembership.php @@ -11,7 +11,7 @@ namespace humhub\modules\space\behaviors; use Yii; use yii\base\Behavior; use yii\base\Exception; -use yii\validators\EmailValidator;; +use yii\validators\EmailValidator; use humhub\modules\user\models\User; use humhub\modules\space\models\Space; use humhub\modules\space\models\Membership; @@ -164,12 +164,11 @@ class SpaceModelMembership extends Behavior */ public function isSpaceOwner($userId = null) { - if(empty($userId) && Yii::$app->user->isGuest) { return false; } else if ($userId instanceof User) { $userId = $userId->id; - } else if (empty($userId)) { + } else if (empty($userId)) { $userId = Yii::$app->user->id; } @@ -422,8 +421,8 @@ class SpaceModelMembership extends Behavior MemberEvent::trigger(Membership::class, Membership::EVENT_MEMBER_ADDED, new MemberEvent([ 'space' => $this->owner, 'user' => $user ])); - - + + // Create Activity MemberAdded::instance()->from($user)->about($this->owner)->save(); @@ -460,6 +459,11 @@ class SpaceModelMembership extends Behavior return true; } + + foreach (Membership::findAll(['user_id' => $userId, 'space_id' => $this->owner->id]) as $membership) { + $membership->delete(); + } + // If was member, create a activity for that if ($membership->status == Membership::STATUS_MEMBER) { $activity = new MemberRemoved(); @@ -473,16 +477,12 @@ class SpaceModelMembership extends Behavior } elseif ($membership->status == Membership::STATUS_INVITED && $membership->originator !== null) { // Was invited, but declined the request - inform originator InviteDeclined::instance() - ->from($user)->about($this->owner)->send($membership->originator); + ->from($user)->about($this->owner)->send($membership->originator); } elseif ($membership->status == Membership::STATUS_APPLICANT) { ApprovalRequestDeclined::instance() ->from(Yii::$app->user->getIdentity())->about($this->owner)->send($user); } - foreach (Membership::findAll(['user_id' => $userId, 'space_id' => $this->owner->id]) as $membership) { - $membership->delete(); - } - ApprovalRequest::instance()->from($user)->about($this->owner)->delete(); InviteNotification::instance()->from($this->owner)->delete($user); diff --git a/protected/humhub/modules/space/behaviors/SpaceModelModules.php b/protected/humhub/modules/space/behaviors/SpaceModelModules.php deleted file mode 100644 index 18d6a8db00..0000000000 --- a/protected/humhub/modules/space/behaviors/SpaceModelModules.php +++ /dev/null @@ -1,179 +0,0 @@ -_availableModules !== null) { - return $this->_availableModules; - } - - $this->_availableModules = array(); - - foreach (Yii::$app->moduleManager->getModules() as $moduleId => $module) { - if ($module instanceof ContentContainerModule && Yii::$app->hasModule($module->id) && $module->hasContentContainerType(Space::className())) { - $this->_availableModules[$module->id] = $module; - } - } - - return $this->_availableModules; - } - - /** - * Returns an array of enabled space modules - * - * @return array - */ - public function getEnabledModules() - { - - if ($this->_enabledModules !== null) { - return $this->_enabledModules; - } - - $this->_enabledModules = array(); - - $availableModules = $this->getAvailableModules(); - $defaultStates = \humhub\modules\space\models\Module::getStates(); - $states = \humhub\modules\space\models\Module::getStates($this->owner->id); - - // Get a list of all enabled module ids - foreach (array_merge(array_keys($defaultStates), array_keys($states)) as $id) { - - // Ensure module Id is available - if (!array_key_exists($id, $availableModules)) { - continue; - } - - if (isset($defaultStates[$id]) && $defaultStates[$id] == \humhub\modules\space\models\Module::STATE_FORCE_ENABLED) { - // Forced enabled globally - $this->_enabledModules[] = $id; - } elseif (!isset($states[$id]) && isset($defaultStates[$id]) && $defaultStates[$id] == \humhub\modules\space\models\Module::STATE_ENABLED) { - // No local state -> global default on - $this->_enabledModules[] = $id; - } elseif (isset($states[$id]) && $states[$id] == \humhub\modules\space\models\Module::STATE_ENABLED) { - // Locally enabled - $this->_enabledModules[] = $id; - } - } - - return $this->_enabledModules; - } - - /** - * Checks if given ModuleId is enabled - * - * @param type $moduleId - */ - public function isModuleEnabled($moduleId) - { - return in_array($moduleId, $this->getEnabledModules()); - } - - /** - * Enables a Module - */ - public function enableModule($moduleId) - { - - // Not enabled globally - if (!array_key_exists($moduleId, $this->getAvailableModules())) { - return false; - } - - // Already enabled module - if ($this->isModuleEnabled($moduleId)) { - Yii::error("Space->enableModule(" . $moduleId . ") module is already enabled"); - return false; - } - - // Add Binding - $spaceModule = \humhub\modules\space\models\Module::findOne(['space_id' => $this->owner->id, 'module_id' => $moduleId]); - if ($spaceModule == null) { - $spaceModule = new \humhub\modules\space\models\Module(); - $spaceModule->space_id = $this->owner->id; - $spaceModule->module_id = $moduleId; - } - $spaceModule->state = \humhub\modules\space\models\Module::STATE_ENABLED; - $spaceModule->save(); - - $module = Yii::$app->moduleManager->getModule($moduleId); - $module->enableContentContainer($this->owner); - - return true; - } - - public function canDisableModule($id) - { - $defaultStates = \humhub\modules\space\models\Module::getStates(0); - if (isset($defaultStates[$id]) && $defaultStates[$id] == \humhub\modules\space\models\Module::STATE_FORCE_ENABLED) { - return false; - } - - return true; - } - - /** - * Uninstalls a Module - */ - public function disableModule($moduleId) - { - - // Not enabled globally - if (!array_key_exists($moduleId, $this->getAvailableModules())) { - return false; - } - - // Already enabled module - if (!$this->isModuleEnabled($moduleId)) { - Yii::error("Space->disableModule(" . $moduleId . ") module is not enabled"); - return false; - } - - // New Way: Handle it directly in module class - $module = Yii::$app->moduleManager->getModule($moduleId); - $module->disableContentContainer($this->owner); - - $spaceModule = \humhub\modules\space\models\Module::findOne(['space_id' => $this->owner->id, 'module_id' => $moduleId]); - if ($spaceModule == null) { - $spaceModule = new \humhub\modules\space\models\Module(); - $spaceModule->space_id = $this->owner->id; - $spaceModule->module_id = $moduleId; - } - $spaceModule->state = \humhub\modules\space\models\Module::STATE_DISABLED; - $spaceModule->save(); - - return true; - } - -} diff --git a/protected/humhub/modules/space/components/UrlRule.php b/protected/humhub/modules/space/components/UrlRule.php index a659cccd53..d446136e37 100644 --- a/protected/humhub/modules/space/components/UrlRule.php +++ b/protected/humhub/modules/space/components/UrlRule.php @@ -2,7 +2,7 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2016 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ @@ -35,15 +35,15 @@ class UrlRule extends Object implements UrlRuleInterface */ public function createUrl($manager, $route, $params) { - if (isset($params['sguid'])) { + if (isset($params['cguid'])) { if ($route == $this->defaultRoute) { $route = ''; } - $urlPart = static::getUrlBySpaceGuid($params['sguid']); + $urlPart = static::getUrlBySpaceGuid($params['cguid']); if ($urlPart !== null) { $url = "s/" . urlencode($urlPart) . "/" . $route; - unset($params['sguid']); + unset($params['cguid']); if (!empty($params) && ($query = http_build_query($params)) !== '') { $url .= '?' . $query; @@ -70,7 +70,7 @@ class UrlRule extends Object implements UrlRuleInterface } $params = $request->get(); - $params['sguid'] = $space->guid; + $params['cguid'] = $space->guid; return [$parts[2], $params]; } @@ -94,10 +94,11 @@ class UrlRule extends Object implements UrlRuleInterface $space = Space::findOne(['guid' => $guid]); if ($space !== null) { static::$spaceUrlMap[$space->guid] = ($space->url != '') ? $space->url : $space->guid; - return static::$spaceUrlMap[$space->guid]; + } else { + static::$spaceUrlMap[$space->guid] = null; } - return null; + return static::$spaceUrlMap[$guid]; } } diff --git a/protected/humhub/modules/space/config.php b/protected/humhub/modules/space/config.php index 71917443f8..fc7e3aa348 100644 --- a/protected/humhub/modules/space/config.php +++ b/protected/humhub/modules/space/config.php @@ -3,12 +3,13 @@ use humhub\modules\search\engine\Search; use humhub\modules\user\models\User; use humhub\modules\space\Events; +use humhub\modules\space\Module; use humhub\components\console\Application; use humhub\commands\IntegrityController; return [ 'id' => 'space', - 'class' => \humhub\modules\space\Module::className(), + 'class' => Module::class, 'isCoreModule' => true, 'urlManagerRules' => [ ['class' => 'humhub\modules\space\components\UrlRule'] @@ -18,11 +19,11 @@ return [ 'class' => 'humhub\modules\space\modules\manage\Module' ], ], - 'events' => array( - array('class' => User::className(), 'event' => User::EVENT_BEFORE_DELETE, 'callback' => array(Events::className(), 'onUserDelete')), - array('class' => Search::className(), 'event' => Search::EVENT_ON_REBUILD, 'callback' => array(Events::className(), 'onSearchRebuild')), - array('class' => Application::className(), 'event' => Application::EVENT_ON_INIT, 'callback' => array(Events::className(), 'onConsoleApplicationInit')), - array('class' => IntegrityController::className(), 'event' => IntegrityController::EVENT_ON_RUN, 'callback' => array(Events::className(), 'onIntegrityCheck')), - ), + 'events' => [ + [User::class, User::EVENT_BEFORE_SOFT_DELETE, [Events::class, 'onUserSoftDelete']], + [Search::class, Search::EVENT_ON_REBUILD, [Events::class, 'onSearchRebuild']], + [Application::class, Application::EVENT_ON_INIT, [Events::class, 'onConsoleApplicationInit']], + [IntegrityController::class, IntegrityController::EVENT_ON_RUN, [Events::class, 'onIntegrityCheck']], + ], ]; ?> \ No newline at end of file diff --git a/protected/humhub/modules/space/controllers/MembershipController.php b/protected/humhub/modules/space/controllers/MembershipController.php index 7b131047be..7d9ddb450d 100644 --- a/protected/humhub/modules/space/controllers/MembershipController.php +++ b/protected/humhub/modules/space/controllers/MembershipController.php @@ -95,12 +95,8 @@ class MembershipController extends \humhub\modules\content\components\ContentCon $this->forcePostRequest(); $space = $this->getSpace(); - if (!$space->canJoin(Yii::$app->user->id)) + if (!$space->canJoin(Yii::$app->user->id)) { throw new HttpException(500, Yii::t('SpaceModule.controllers_SpaceController', 'You are not allowed to join this space!')); - - if ($space->join_policy == Space::JOIN_POLICY_APPLICATION) { - // Redirect to Membership Request Form - return $this->redirect($this->createUrl('//space/space/requestMembershipForm', ['sguid' => $this->getSpace()->guid])); } $space->addMember(Yii::$app->user->id); diff --git a/protected/humhub/modules/space/controllers/SpaceController.php b/protected/humhub/modules/space/controllers/SpaceController.php index e03ca25170..2c7c49d7c4 100644 --- a/protected/humhub/modules/space/controllers/SpaceController.php +++ b/protected/humhub/modules/space/controllers/SpaceController.php @@ -33,11 +33,6 @@ use humhub\modules\post\permissions\CreatePost; class SpaceController extends ContentContainerController { - /** - * @inheritdoc - */ - public $hideSidebar = false; - /** * @inheritdoc */ diff --git a/protected/humhub/modules/space/helpers/MembershipHelper.php b/protected/humhub/modules/space/helpers/MembershipHelper.php new file mode 100644 index 0000000000..8d54cf0a93 --- /dev/null +++ b/protected/humhub/modules/space/helpers/MembershipHelper.php @@ -0,0 +1,46 @@ +user->getIdentity(); + } + + $spaces = []; + foreach (Membership::GetUserSpaces($user->id) as $space) { + if ($space->isSpaceOwner($user->id)) { + $spaces[] = $space; + } + } + return $spaces; + } + +} diff --git a/protected/humhub/modules/space/migrations/m160205_203913_foreign_keys.php b/protected/humhub/modules/space/migrations/m160205_203913_foreign_keys.php index 969869141e..705e0add8a 100644 --- a/protected/humhub/modules/space/migrations/m160205_203913_foreign_keys.php +++ b/protected/humhub/modules/space/migrations/m160205_203913_foreign_keys.php @@ -26,7 +26,9 @@ class m160205_203913_foreign_keys extends Migration try { $this->alterColumn('space_module', 'space_id', $this->integer()->null()); $this->update('space_module', ['space_id' => new yii\db\Expression('NULL')], ['space_id' => 0]); - $this->addForeignKey('fk_space_module-space_id', 'space_module', 'space_id', 'space', 'id', 'CASCADE', 'CASCADE'); + + # Not required in 1.3 + #$this->addForeignKey('fk_space_module-space_id', 'space_module', 'space_id', 'space', 'id', 'CASCADE', 'CASCADE'); } catch (Exception $ex) { Yii::error($ex->getMessage()); } diff --git a/protected/humhub/modules/space/models/Membership.php b/protected/humhub/modules/space/models/Membership.php index 31b13f9f60..328f2170b7 100644 --- a/protected/humhub/modules/space/models/Membership.php +++ b/protected/humhub/modules/space/models/Membership.php @@ -11,6 +11,7 @@ namespace humhub\modules\space\models; use Yii; use humhub\modules\user\models\User; use humhub\modules\space\models\Space; +use humhub\components\ActiveRecord; /** * This is the model class for table "space_membership". @@ -32,7 +33,7 @@ use humhub\modules\space\models\Space; * * @property Space $space */ -class Membership extends \yii\db\ActiveRecord +class Membership extends ActiveRecord { /** @@ -155,7 +156,7 @@ class Membership extends \yii\db\ActiveRecord /** * Returns a list of all spaces of the given userId * - * @param integer $userId the user id + * @param int $userId the user id or empty for current user * @param boolean $cached use cached result if available * @return Space[] an array of spaces */ diff --git a/protected/humhub/modules/space/models/Module.php b/protected/humhub/modules/space/models/Module.php deleted file mode 100644 index 2d617ff0a5..0000000000 --- a/protected/humhub/modules/space/models/Module.php +++ /dev/null @@ -1,132 +0,0 @@ - 255] - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'id' => 'ID', - 'module_id' => 'Module ID', - 'space_id' => 'Space ID', - 'state' => 'State', - ]; - } - - /** - * @inheritdoc - */ - public function beforeSave($insert) - { - Yii::$app->cache->delete(self::STATES_CACHE_ID_PREFIX . $this->space_id); - return parent::beforeSave($insert); - } - - /** - * @inheritdoc - */ - public function beforeDelete() - { - Yii::$app->cache->delete(self::STATES_CACHE_ID_PREFIX . $this->space_id); - - return parent::beforeDelete(); - } - - /** - * Returns an array of moduleId and the their states (enabled, disabled, force enabled) - * for given space id. If space id is 0 or empty, the default states will be returned. - * - * @param int|null $spaceId the space id or null for the default state - * @return array State of Module Ids - */ - public static function getStates($spaceId = null) - { - - // Used already cached values - if (isset(self::$_states[$spaceId])) { - return self::$_states[$spaceId]; - } - - $states = Yii::$app->cache->get(self::STATES_CACHE_ID_PREFIX . $spaceId); - if ($states === false) { - - $states = []; - - $query = self::find(); - - if (empty($spaceId)) { - $query->andWhere(['IS', 'space_id', new \yii\db\Expression('NULL')]); - } else { - $query->andWhere(['space_id' => $spaceId]); - } - - foreach ($query->all() as $spaceModule) { - $states[$spaceModule->module_id] = $spaceModule->state; - } - - Yii::$app->cache->set(self::STATES_CACHE_ID_PREFIX . $spaceId, $states); - } - self::$_states[$spaceId] = $states; - - return self::$_states[$spaceId]; - } - - /** - * Returns space relation - * - * @return ActiveQuery the relation query - */ - public function getSpace() - { - return $this->hasOne(Space::className(), ['id' => 'space_id']); - } - -} diff --git a/protected/humhub/modules/space/models/Space.php b/protected/humhub/modules/space/models/Space.php index f2ecaeec98..d7cc01e920 100644 --- a/protected/humhub/modules/space/models/Space.php +++ b/protected/humhub/modules/space/models/Space.php @@ -2,22 +2,22 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2016 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ namespace humhub\modules\space\models; -use humhub\modules\space\widgets\Members; use Yii; - use humhub\modules\space\permissions\CreatePrivateSpace; use humhub\modules\space\permissions\CreatePublicSpace; use humhub\modules\space\components\UrlValidator; use humhub\modules\content\models\Content; use humhub\modules\content\components\ContentContainerActiveRecord; use humhub\modules\user\models\User; -use yii\helpers\Url; +use humhub\modules\user\models\Follow; +use humhub\modules\user\models\Invite; +use humhub\modules\user\models\Group; /** * This is the model class for table "space". @@ -39,6 +39,7 @@ use yii\helpers\Url; * @property integer $contentcontainer_id * @property integer $default_content_visibility * @property string $color + * @property User $ownerUser the owner of this space */ class Space extends ContentContainerActiveRecord implements \humhub\modules\search\interfaces\Searchable { @@ -63,6 +64,16 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear const USERGROUP_USER = 'user'; const USERGROUP_GUEST = 'guest'; + /** + * @inheritdoc + */ + public $controllerBehavior = \humhub\modules\space\behaviors\SpaceController::class; + + /** + * @inheritdoc + */ + public $defaultRoute = '/space/space'; + /** * @inheritdoc */ @@ -135,7 +146,7 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear return [ 'visibility' => Yii::t('SpaceModule.views_admin_edit', 'Choose the security level for this workspace to define the visibleness.'), 'join_policy' => Yii::t('SpaceModule.views_admin_edit', 'Choose the kind of membership you want to provide for this workspace.'), - 'default_content_visibility' => Yii::t('SpaceModule.views_admin_edit', 'Choose if new content should be public or private by default') + 'default_content_visibility' => Yii::t('SpaceModule.views_admin_edit', 'Choose if new content should be public or private by default') ]; } @@ -145,11 +156,11 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear public function behaviors() { return array( - \humhub\components\behaviors\GUID::className(), - \humhub\modules\content\components\behaviors\SettingsBehavior::className(), - \humhub\modules\space\behaviors\SpaceModelModules::className(), - \humhub\modules\space\behaviors\SpaceModelMembership::className(), - \humhub\modules\user\behaviors\Followable::className(), + \humhub\components\behaviors\GUID::class, + \humhub\modules\content\components\behaviors\SettingsBehavior::class, + \humhub\modules\space\behaviors\SpaceModelMembership::class, + \humhub\modules\user\behaviors\Followable::class, + \humhub\modules\content\components\behaviors\CompatModuleManager::class, ); } @@ -197,7 +208,7 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear $this->url = mb_strtolower($this->url); } - if($this->visibility == self::VISIBILITY_NONE) { + if ($this->visibility == self::VISIBILITY_NONE) { $this->join_policy = self::JOIN_POLICY_NONE; $this->default_content_visibility = Content::VISIBILITY_PRIVATE; } @@ -216,20 +227,25 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear } } + foreach ($this->moduleManager->getEnabled() as $module) { + $this->moduleManager->disable($module); + } + Yii::$app->search->delete($this); $this->getProfileImage()->delete(); + $this->getProfileBannerImage()->delete(); - \humhub\modules\user\models\Follow::deleteAll(['object_id' => $this->id, 'object_model' => 'Space']); + Follow::deleteAll(['object_id' => $this->id, 'object_model' => 'Space']); foreach (Membership::findAll(['space_id' => $this->id]) as $spaceMembership) { $spaceMembership->delete(); } - \humhub\modules\user\models\Invite::deleteAll(['space_invite_id' => $this->id]); + Invite::deleteAll(['space_invite_id' => $this->id]); // When this workspace is used in a group as default workspace, delete the link - foreach (\humhub\modules\user\models\Group::findAll(['space_id' => $this->id]) as $group) { + foreach (Group::findAll(['space_id' => $this->id]) as $group) { $group->space_id = ""; $group->save(); } @@ -333,14 +349,6 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear return $attributes; } - /** - * Returns the Search Result Output - */ - public function getSearchResult() - { - return Yii::$app->getController()->widget('application.modules_core.space.widgets.SpaceSearchResultWidget', array('space' => $this), true); - } - /** * Checks if space has tags * @@ -390,31 +398,6 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear return $this->status === self::STATUS_ARCHIVED; } - /** - * Creates an url in space scope. - * (Adding sguid parameter to identify current space.) - * See CController createUrl() for more details. - * - * @since 0.9 - * @param string $route the URL route. - * @param array $params additional GET parameters. - * @param boolean|string $scheme whether to create an absolute URL and if it is a string, the scheme (http or https) to use. - * @return string - */ - public function createUrl($route = null, $params = array(), $scheme = false) - { - if ($route == null) { - $route = '/space/space'; - } - - array_unshift($params, $route); - if (!isset($params['sguid'])) { - $params['sguid'] = $this->guid; - } - - return Url::toRoute($params, $scheme); - } - /** * Validator for visibility * @@ -509,6 +492,14 @@ class Space extends ContentContainerActiveRecord implements \humhub\modules\sear return $query; } + /** + * @return \yii\db\ActiveQuery + */ + public function getOwnerUser() + { + return $this->hasOne(User::class, ['id' => 'created_by']); + } + /** * Return user groups * diff --git a/protected/humhub/modules/space/modules/manage/components/Controller.php b/protected/humhub/modules/space/modules/manage/components/Controller.php index 89a46f9bf6..0c9aa9c4f3 100644 --- a/protected/humhub/modules/space/modules/manage/components/Controller.php +++ b/protected/humhub/modules/space/modules/manage/components/Controller.php @@ -19,12 +19,7 @@ use yii\web\HttpException; */ class Controller extends \humhub\modules\content\components\ContentContainerController { - /** - * @inheritdoc - */ - public $hideSidebar = true; - protected function getAccessRules() { return [ ['login'], diff --git a/protected/humhub/modules/space/modules/manage/models/MembershipSearch.php b/protected/humhub/modules/space/modules/manage/models/MembershipSearch.php index 6b89e46d5d..e15599e04d 100644 --- a/protected/humhub/modules/space/modules/manage/models/MembershipSearch.php +++ b/protected/humhub/modules/space/modules/manage/models/MembershipSearch.php @@ -8,10 +8,11 @@ namespace humhub\modules\space\modules\manage\models; - +use Yii; use yii\base\Model; use yii\data\ActiveDataProvider; use humhub\modules\space\models\Membership; +use humhub\modules\space\models\Space; /** * Description of GroupSearch @@ -21,6 +22,16 @@ use humhub\modules\space\models\Membership; class MembershipSearch extends Membership { + /** + * @var string the freetext search string + */ + public $freeText; + + /** + * @var string the role id + */ + public $roleId; + /** * @var int Status of members to display */ @@ -42,7 +53,7 @@ class MembershipSearch extends Membership { return [ [['user_id', 'status'], 'integer'], - [['user.profile.firstname', 'user.profile.lastname', 'user.username', 'group_id'], 'safe'], + [['user.profile.firstname', 'user.profile.lastname', 'user.username', 'group_id', 'freeText'], 'safe'], ]; } @@ -83,8 +94,13 @@ class MembershipSearch extends Membership 'asc' => ['profile.lastname' => SORT_ASC], 'desc' => ['profile.lastname' => SORT_DESC], ], + 'profile.lastname' => [ + 'asc' => ['profile.lastname' => SORT_ASC], + 'desc' => ['profile.lastname' => SORT_DESC], + ], 'user.username', 'last_visit', + 'created_at', 'group_id', ]]); @@ -97,6 +113,21 @@ class MembershipSearch extends Membership $query->andWhere(['space_membership.space_id' => $this->space_id]); + // Freetext filters + if (!empty($this->freeText)) { + $query->andWhere([ + 'OR', + ['like', 'user.id', $this->freeText], + ['like', 'user.username', $this->freeText], + ['like', 'user.email', $this->freeText], + ['like', 'profile.firstname', $this->freeText], + ['like', 'profile.lastname', $this->freeText] + ]); + } + if (!empty($this->group_id)) { + $query->andFilterWhere(['space_membership.group_id' => $this->group_id]); + } + $query->andFilterWhere(['space_membership.group_id' => $this->group_id]); $query->andFilterWhere(['like', 'profile.lastname', $this->getAttribute('user.profile.lastname')]); $query->andFilterWhere(['like', 'profile.firstname', $this->getAttribute('user.profile.firstname')]); @@ -105,4 +136,13 @@ class MembershipSearch extends Membership return $dataProvider; } + public function getRoles(Space $space) + { + $groups = $space->getUserGroups(); + unset($groups[Space::USERGROUP_OWNER], $groups[Space::USERGROUP_GUEST], $groups[Space::USERGROUP_USER]); + $groups = array_merge(['' => Yii::t('SpaceModule.manage', 'Show all')], $groups); + + return $groups; + } + } diff --git a/protected/humhub/modules/space/modules/manage/views/member/index.php b/protected/humhub/modules/space/modules/manage/views/member/index.php index 6fd1b54f77..3b4f3211af 100644 --- a/protected/humhub/modules/space/modules/manage/views/member/index.php +++ b/protected/humhub/modules/space/modules/manage/views/member/index.php @@ -1,9 +1,13 @@
    @@ -12,6 +16,22 @@ use humhub\modules\space\modules\manage\widgets\MemberMenu;
    $space]); ?>
    + + 'get']); ?> +
    +
    +
    + 'form-control', 'placeholder' => Yii::t('AdminModule.user', 'Search by name, email or id.')]); ?> + + + +
    +
    +
    + 'form-control', 'onchange' => 'this.form.submit()']); ?> +
    +
    +
    $dataProvider, - 'filterModel' => $searchModel, + 'summary' => '', 'columns' => [ - 'user.username', - 'user.profile.firstname', - 'user.profile.lastname', + ['class' => ImageColumn::class, 'userAttribute' => 'user'], + ['class' => DisplayNameColumn::class, 'userAttribute' => 'user'], [ - 'label' => Yii::t('SpaceModule.views_admin_members', 'Role'), - 'class' => 'humhub\libs\DropDownGridColumn', - 'attribute' => 'group_id', - 'submitAttributes' => ['user_id'], - 'readonly' => function ($data) use ($space) { - if ($space->isSpaceOwner($data->user->id)) { - return true; - } - return false; - }, - 'filter' => $groups, - 'dropDownOptions' => $groups, + 'label' => 'Member since', + 'attribute' => 'created_at', + 'format' => 'raw', 'value' => - function ($data) use (&$groups, $space) { - return $groups[$data->group_id]; - } + function ($data) use (&$groups) { + if ($data->created_at == '') { + return Yii::t('SpaceModule.views_admin_members', '-'); + } + + return humhub\widgets\TimeAgo::widget(['timestamp' => $data->last_visit]); + } ], [ 'attribute' => 'last_visit', @@ -54,28 +68,47 @@ use humhub\modules\space\modules\manage\widgets\MemberMenu; return humhub\widgets\TimeAgo::widget(['timestamp' => $data->last_visit]); } - ], - [ - 'header' => Yii::t('SpaceModule.views_admin_members', 'Actions'), - 'class' => 'yii\grid\ActionColumn', - 'buttons' => [ - 'view' => function () { - return; - }, - 'delete' => function ($url, $model) use ($space) { - if ($space->isSpaceOwner($model->user->id) || Yii::$app->user->id == $model->user->id) { - return; - } - return Html::a(Yii::t('SpaceModule.views_admin_members', 'Remove'), $space->createUrl('remove', ['userGuid' => $model->user->guid]), ['class' => 'btn btn-danger btn-sm', 'data-method' => 'POST', 'data-confirm' => 'Are you sure?']); - }, - 'update' => function () { - return; - }, - ], - ], - ], - ]); - ?> + ], + [ + 'label' => Yii::t('SpaceModule.views_admin_members', 'Role'), + 'class' => 'humhub\libs\DropDownGridColumn', + 'attribute' => 'group_id', + 'submitAttributes' => ['user_id'], + 'readonly' => function ($data) use ($space) { + if ($space->isSpaceOwner($data->user->id)) { + return true; + } + return false; + }, + 'filter' => $groups, + 'dropDownOptions' => $groups, + 'value' => + function ($data) use (&$groups, $space) { + return $groups[$data->group_id]; + } + ], + [ + 'class' => 'yii\grid\ActionColumn', + 'options' => ['style' => 'width:40px; min-width:40px;'], + 'buttons' => [ + 'view' => function($url, $model) { + return false; + }, + 'update' => function($url, $model) { + return false; + }, + 'delete' => function($url, $model) { + return Html::a('', '#', [ + 'title' => Yii::t('SpaceModule.manage', 'Remove from space'), + 'class' => 'btn btn-danger btn-xs tt', + 'data-confirm' => 'Are you really sure?' + ]); + } + ], + ], + ], + ]); + ?>
    diff --git a/protected/humhub/modules/space/views/space/_layout.php b/protected/humhub/modules/space/views/space/_layout.php index 269a020ae5..b3e0ab23f5 100755 --- a/protected/humhub/modules/space/views/space/_layout.php +++ b/protected/humhub/modules/space/views/space/_layout.php @@ -1,47 +1,28 @@ context->contentContainer; ?>
    - $space]); ?> - + $space]); ?>
    - $space]); ?> + $space]); ?>
    - - context->hideSidebar) && $this->context->hideSidebar) : ?> -
    - $space, - 'content' => $content - ]) ?> -
    - -
    - $space, - 'content' => $content - ]) ?> -
    -
    - $space, 'widgets' => [ - [\humhub\modules\activity\widgets\Stream::className(), ['streamAction' => '/space/space/stream', 'contentContainer' => $space], ['sortOrder' => 10]], - [\humhub\modules\space\modules\manage\widgets\PendingApprovals::className(), ['space' => $space], ['sortOrder' => 20]], - [\humhub\modules\space\widgets\Members::className(), ['space' => $space], ['sortOrder' => 30]] - ]]); - ?> -
    - +
    + $space, 'content' => $content]) ?> +
    diff --git a/protected/humhub/modules/space/views/space/_sidebar.php b/protected/humhub/modules/space/views/space/_sidebar.php new file mode 100755 index 0000000000..8f8ac4c806 --- /dev/null +++ b/protected/humhub/modules/space/views/space/_sidebar.php @@ -0,0 +1,14 @@ +
    +
    + +
    +
    + $space, 'widgets' => [ + [\humhub\modules\activity\widgets\Stream::className(), ['streamAction' => '/space/space/stream', 'contentContainer' => $space], ['sortOrder' => 10]], + [\humhub\modules\space\modules\manage\widgets\PendingApprovals::className(), ['space' => $space], ['sortOrder' => 20]], + [\humhub\modules\space\widgets\Members::className(), ['space' => $space], ['sortOrder' => 30]] + ]]); + ?> +
    +
    \ No newline at end of file diff --git a/protected/humhub/modules/space/views/space/home.php b/protected/humhub/modules/space/views/space/home.php index befcd9e59f..8383931857 100644 --- a/protected/humhub/modules/space/views/space/home.php +++ b/protected/humhub/modules/space/views/space/home.php @@ -1,3 +1,5 @@ + +beginContent('@space/views/space/_sidebar.php', ['space' => $space]); ?> $space]); ?> $emptyMessage, 'messageStreamEmptyCss' => ($canCreatePosts) ? 'placeholder-empty-stream' : '', ]); -?> \ No newline at end of file +?> +endContent(); ?> \ No newline at end of file diff --git a/protected/humhub/modules/space/widgets/BrowseMenu.php b/protected/humhub/modules/space/widgets/BrowseMenu.php index 605cda9645..f2cf3c78e0 100644 --- a/protected/humhub/modules/space/widgets/BrowseMenu.php +++ b/protected/humhub/modules/space/widgets/BrowseMenu.php @@ -48,21 +48,6 @@ class BrowseMenu extends MenuWidget 'isActive' => (Yii::app()->controller->id == "spacebrowse" && Yii::app()->controller->action->id == "index"), )); - -# $this->addItem(array( -# 'label' => Yii::t('SpaceModule.widgets_SpaceBrowseMenuWidget', 'Members'), -# 'url' => Yii::app()->createUrl('//space/space/members', array('sguid'=>$spaceGuid)), -# 'sortOrder' => 200, -# 'isActive' => (Yii::app()->controller->id == "space" && Yii::app()->controller->action->id == "members"), -# )); -# $this->addItem(array( -# 'label' => Yii::t('SpaceModule.widgets_SpaceBrowseMenuWidget', 'Admin'), -# 'url' => Yii::app()->createUrl('//space/admin', array('sguid'=>$spaceGuid)), -# 'sortOrder' => 9999, -# 'isActive' => (Yii::app()->controller->id == "admin" && Yii::app()->controller->action->id == "index"), -# )); - - parent::init(); } diff --git a/protected/humhub/modules/space/widgets/ChangeImage.php b/protected/humhub/modules/space/widgets/ChangeImage.php deleted file mode 100644 index 872aa5d7b0..0000000000 --- a/protected/humhub/modules/space/widgets/ChangeImage.php +++ /dev/null @@ -1,24 +0,0 @@ -render('changeImage', array( - )); - } - -} - -?> diff --git a/protected/humhub/modules/space/widgets/Image.php b/protected/humhub/modules/space/widgets/Image.php index a95907b915..120c40d560 100644 --- a/protected/humhub/modules/space/widgets/Image.php +++ b/protected/humhub/modules/space/widgets/Image.php @@ -52,6 +52,18 @@ class Image extends Widget */ public $linkOptions = []; + /** + * @var string show tooltip with further information about the user (Only available when link is true) + * @since 1.3 + */ + public $showTooltip = false; + + /** + * @var string the tooltip text (default is users display name) + * @since 1.3 + */ + public $tooltipText = null; + /** * @inheritdoc */ @@ -100,6 +112,15 @@ class Image extends Widget $imageHtmlOptions['style'] .= " width: " . $this->width . "px; height: " . $this->height . "px"; $imageHtmlOptions['alt'] = Html::encode($this->space->name); + + if ($this->showTooltip) { + $this->linkOptions['data-toggle'] = 'tooltip'; + $this->linkOptions['data-placement'] = 'top'; + $this->linkOptions['data-original-title'] = ($this->tooltipText) ? $this->tooltipText : Html::encode($this->space->name); + Html::addCssClass($this->linkOptions, 'tt'); + } + + $defaultImage = (basename($this->space->getProfileImage()->getUrl()) == 'default_space.jpg' || basename($this->space->getProfileImage()->getUrl()) == 'default_space.jpg?cacheId=0') ? true : false; if (!$defaultImage) { @@ -108,7 +129,7 @@ class Image extends Widget $imageHtmlOptions['class'] .= " hidden"; } - return $this->render('image', [ + return $this->render('@space/widgets/views/image', [ 'space' => $this->space, 'acronym' => $this->getAcronym(), 'link' => $this->link, diff --git a/protected/humhub/modules/space/widgets/views/changeImage.php b/protected/humhub/modules/space/widgets/views/changeImage.php deleted file mode 100644 index 46076bb9ca..0000000000 --- a/protected/humhub/modules/space/widgets/views/changeImage.php +++ /dev/null @@ -1,9 +0,0 @@ -
    -
    -
    -

    - createUrl('//space/admin/changeImage', array('sguid' => $this->getController()->getSpace()->guid)), array('class' => 'btn btn-primary')); ?> - -
    -
    -
    diff --git a/protected/humhub/modules/user/Events.php b/protected/humhub/modules/user/Events.php index a242ebad94..b998bc4921 100644 --- a/protected/humhub/modules/user/Events.php +++ b/protected/humhub/modules/user/Events.php @@ -128,15 +128,6 @@ class Events extends \yii\base\Object } } - $integrityController->showTestHeadline("User Module - Modules (" . models\Module::find()->count() . " entries)"); - foreach (models\Module::find()->joinWith(['user'])->each() as $module) { - if ($module->user == null) { - if ($integrityController->showFix("Deleting user-module " . $module->id . " of non existing user!")) { - $module->delete(); - } - } - } - $userIds = User::find()->select('id')->asArray()->all(); foreach ($userIds as $key => $id) { $userIds[$key] = $id['id']; @@ -158,19 +149,8 @@ class Events extends \yii\base\Object */ public static function onHourlyCron($event) { - foreach (Yii::$app->authClientCollection->getClients() as $authClient) { - if ($authClient instanceof authclient\interfaces\AutoSyncUsers) { - /** - * @var authclient\interfaces\AutoSyncUsers $authClient - */ - $authClient->syncUsers(); - } - } - - // Delete expired session - foreach (models\Session::find()->where(['<', 'expire', time()])->all() as $session) { - $session->delete(); - } + Yii::$app->queue->push(new jobs\SyncUsers()); + Yii::$app->queue->push(new jobs\DeleteExpiredSessions()); } } diff --git a/protected/humhub/modules/user/Module.php b/protected/humhub/modules/user/Module.php index c52718ab51..96f932ba65 100644 --- a/protected/humhub/modules/user/Module.php +++ b/protected/humhub/modules/user/Module.php @@ -70,19 +70,25 @@ class Module extends \humhub\components\Module * @since 1.2 */ public $displayNameCallback = null; - + /** * @var boolean defines if the user following is disabled or not. * @since 1.2 */ public $disableFollow = false; - + /** * @var boolean defines mark user e-mail field as required * @since 1.2.2 */ public $emailRequired = true; + /** + * @var array profile field names to keep after user soft deletion + * @since 1.3 + */ + public $softDeleteKeepProfileFields = ['firstname', 'lastname']; + /** * @inheritdoc */ diff --git a/protected/humhub/modules/user/authclient/ZendLdapClient.php b/protected/humhub/modules/user/authclient/ZendLdapClient.php index 5552987768..a2b515f0a0 100644 --- a/protected/humhub/modules/user/authclient/ZendLdapClient.php +++ b/protected/humhub/modules/user/authclient/ZendLdapClient.php @@ -333,7 +333,12 @@ class ZendLdapClient extends BaseFormAuth implements interfaces\AutoSyncUsers, i try { $this->getLdap()->bind($userName, $this->login->password); - return $this->getLdap()->getCanonicalAccountName($userName, Ldap::ACCTNAME_FORM_DN); + $dn = $this->getLdap()->getCanonicalAccountName($userName, Ldap::ACCTNAME_FORM_DN); + + // Rebind with administrative DN + $this->getLdap()->bind(); + + return $dn; } catch (LdapException $ex) { // User not found in LDAP } diff --git a/protected/humhub/modules/user/behaviors/ProfileController.php b/protected/humhub/modules/user/behaviors/ProfileController.php index 8683a067a2..e6e92a74e2 100644 --- a/protected/humhub/modules/user/behaviors/ProfileController.php +++ b/protected/humhub/modules/user/behaviors/ProfileController.php @@ -1,21 +1,9 @@ 'beforeAction', + Controller::EVENT_BEFORE_ACTION => 'beforeAction', ]; } - public function getUser() + /** + * @inheritdoc + */ + public function attach($owner) { - if ($this->user != null) { - return $this->user; + parent::attach($owner); + + if (!$this->owner->contentContainer instanceof User) { + throw new \yii\base\InvalidValueException('Invalid contentcontainer type of controller.'); } - $guid = Yii::$app->request->getQuery('uguid'); - $this->user = User::findOne(['guid' => $guid]); - - if ($this->user == null) - throw new HttpException(404, Yii::t('UserModule.behaviors_ProfileControllerBehavior', 'User not found!')); - - $this->checkAccess(); + $this->user = $this->owner->contentContainer; + } + /** + * + * @return type + */ + public function getUser() + { return $this->user; } - public function checkAccess() + public function beforeAction($action) { if ($this->user->status == User::STATUS_NEED_APPROVAL) { throw new HttpException(404, Yii::t('UserModule.behaviors_ProfileControllerBehavior', 'This user account is not approved yet!')); } + + if ($this->user->status == User::STATUS_SOFT_DELETED) { + throw new HttpException(404, Yii::t('UserModule.behaviors_ProfileControllerBehavior', 'This profile is no longer available!')); + } + if (Yii::$app->getModule('user')->settings->get('auth.allowGuestAccess') && $this->user->visibility != User::VISIBILITY_ALL && Yii::$app->user->isGuest) { throw new HttpException(401, Yii::t('UserModule.behaviors_ProfileControllerBehavior', 'You need to login to view this user profile!')); } - } - - public function beforeAction($action) { $this->owner->prependPageTitle($this->user->displayName); + $this->owner->subLayout = "@humhub/modules/user/views/profile/_layout"; } } diff --git a/protected/humhub/modules/user/behaviors/UserModelModules.php b/protected/humhub/modules/user/behaviors/UserModelModules.php deleted file mode 100644 index 05e56b2c4d..0000000000 --- a/protected/humhub/modules/user/behaviors/UserModelModules.php +++ /dev/null @@ -1,180 +0,0 @@ -_availableModules !== null) { - return $this->_availableModules; - } - - $this->_availableModules = array(); - - foreach (Yii::$app->moduleManager->getModules() as $moduleId => $module) { - if ($module instanceof ContentContainerModule && Yii::$app->hasModule($module->id) && $module->hasContentContainerType(User::className())) { - $this->_availableModules[$module->id] = $module; - } - } - - return $this->_availableModules; - } - - /** - * Returns an array of enabled user modules - * - * @return array - */ - public function getEnabledModules() - { - - if ($this->_enabledModules !== null) { - return $this->_enabledModules; - } - - $this->_enabledModules = array(); - - $availableModules = $this->getAvailableModules(); - $defaultStates = \humhub\modules\user\models\Module::getStates(); - $states = \humhub\modules\user\models\Module::getStates($this->owner->id); - - // Get a list of all enabled module ids - foreach (array_merge(array_keys($defaultStates), array_keys($states)) as $id) { - - // Ensure module Id is available - if (!array_key_exists($id, $availableModules)) { - continue; - } - - if (isset($defaultStates[$id]) && $defaultStates[$id] == \humhub\modules\user\models\Module::STATE_FORCE_ENABLED) { - // Forced enabled globally - $this->_enabledModules[] = $id; - } elseif (!isset($states[$id]) && isset($defaultStates[$id]) && $defaultStates[$id] == \humhub\modules\user\models\Module::STATE_ENABLED) { - // No local state -> global default on - $this->_enabledModules[] = $id; - } elseif (isset($states[$id]) && $states[$id] == \humhub\modules\user\models\Module::STATE_ENABLED) { - // Locally enabled - $this->_enabledModules[] = $id; - } - } - - return $this->_enabledModules; - } - - /** - * Checks if given ModuleId is enabled - * - * @param type $moduleId - */ - public function isModuleEnabled($moduleId) - { - return in_array($moduleId, $this->getEnabledModules()); - } - - /** - * Enables a Module - */ - public function enableModule($moduleId) - { - - // Not enabled globally - if (!array_key_exists($moduleId, $this->getAvailableModules())) { - return false; - } - - // Already enabled module - if ($this->isModuleEnabled($moduleId)) { - Yii::error("User->enableModule(" . $moduleId . ") module is already enabled"); - return false; - } - - // Add Binding - $userModule = \humhub\modules\user\models\Module::findOne(['user_id' => $this->owner->id, 'module_id' => $moduleId]); - if ($userModule == null) { - $userModule = new \humhub\modules\user\models\Module(); - $userModule->user_id = $this->owner->id; - $userModule->module_id = $moduleId; - } - $userModule->state = \humhub\modules\user\models\Module::STATE_ENABLED; - $userModule->save(); - - $module = Yii::$app->moduleManager->getModule($moduleId); - $module->enableContentContainer($this->owner); - - return true; - } - - public function canDisableModule($id) - { - $defaultStates = \humhub\modules\user\models\Module::getStates(); - if (isset($defaultStates[$id]) && $defaultStates[$id] == \humhub\modules\user\models\Module::STATE_FORCE_ENABLED) { - return false; - } - - return true; - } - - /** - * Disables a Module - */ - public function disableModule($moduleId) - { - - // Not enabled globally - if (!array_key_exists($moduleId, $this->getAvailableModules())) { - return false; - } - - // Already enabled module - if (!$this->isModuleEnabled($moduleId)) { - Yii::error("User->disableModule(" . $moduleId . ") module is not enabled"); - return false; - } - - // New Way: Handle it directly in module class - $module = Yii::$app->moduleManager->getModule($moduleId); - $module->disableContentContainer($this->owner); - - $userModule = \humhub\modules\user\models\Module::findOne(['user_id' => $this->owner->id, 'module_id' => $moduleId]); - if ($userModule == null) { - $userModule = new \humhub\modules\user\models\Module; - $userModule->user_id = $this->owner->id; - $userModule->module_id = $moduleId; - } - $userModule->state = \humhub\modules\user\models\Module::STATE_DISABLED; - $userModule->save(); - - return true; - } - -} diff --git a/protected/humhub/modules/user/components/CheckPasswordValidator.php b/protected/humhub/modules/user/components/CheckPasswordValidator.php index ad58f0705a..ed4f91a54c 100644 --- a/protected/humhub/modules/user/components/CheckPasswordValidator.php +++ b/protected/humhub/modules/user/components/CheckPasswordValidator.php @@ -2,7 +2,7 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2016 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ @@ -10,6 +10,7 @@ namespace humhub\modules\user\components; use Yii; use yii\validators\Validator; +use humhub\modules\user\models\User; /** * CheckPasswordValidator checks password of currently logged in user. @@ -19,6 +20,11 @@ use yii\validators\Validator; class CheckPasswordValidator extends Validator { + /** + * @var User the user + */ + public $user; + /** * @inheritdoc */ @@ -26,25 +32,31 @@ class CheckPasswordValidator extends Validator { $value = $object->$attribute; - $user = Yii::$app->user->getIdentity(); - if ($user->currentPassword !== null && !$user->currentPassword->validatePassword($value)) { - $object->addError($attribute, Yii::t('UserModule.components_CheckPasswordValidator', "Your password is incorrect!")); + if ($this->user === null) { + $this->user = Yii::$app->user->getIdentity(); + } + + if ($this->user->currentPassword !== null && !$this->user->currentPassword->validatePassword($value)) { + $object->addError($attribute, Yii::t('UserModule.password', "Your password is incorrect!")); } } /** * Checks if current user has a password set. * + * @param User $user the user or null for current * @return boolean */ - public static function hasPassword() + public static function hasPassword(User $user = null) { - $user = Yii::$app->user->getIdentity(); - + if ($user === null) { + $user = Yii::$app->user->getIdentity(); + } + if ($user === null) { return false; } - + return ($user->currentPassword !== null); } diff --git a/protected/humhub/modules/user/components/Session.php b/protected/humhub/modules/user/components/Session.php index c435f46c8f..7b1c2f5647 100644 --- a/protected/humhub/modules/user/components/Session.php +++ b/protected/humhub/modules/user/components/Session.php @@ -20,6 +20,11 @@ use yii\db\Expression; class Session extends DbSession { + /** + * @inheritdoc + */ + public $sessionTable = 'user_http_session'; + /** * Returns all current logged in users. * diff --git a/protected/humhub/modules/user/components/UrlRule.php b/protected/humhub/modules/user/components/UrlRule.php index 1bff1e847e..3bc2c42496 100644 --- a/protected/humhub/modules/user/components/UrlRule.php +++ b/protected/humhub/modules/user/components/UrlRule.php @@ -2,7 +2,7 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2015 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ @@ -35,10 +35,10 @@ class UrlRule extends Object implements UrlRuleInterface */ public function createUrl($manager, $route, $params) { - if (isset($params['uguid'])) { - $username = static::getUrlByUserGuid($params['uguid']); + if (isset($params['cguid'])) { + $username = static::getUrlByUserGuid($params['cguid']); if ($username !== null) { - unset($params['uguid']); + unset($params['cguid']); if ($this->defaultRoute == $route) { $route = ""; @@ -69,7 +69,7 @@ class UrlRule extends Object implements UrlRuleInterface $parts[2] = $this->defaultRoute; } $params = $request->get(); - $params['uguid'] = $user->guid; + $params['cguid'] = $user->guid; return [$parts[2], $params]; } @@ -91,12 +91,8 @@ class UrlRule extends Object implements UrlRuleInterface } $user = User::findOne(['guid' => $guid]); - if ($user !== null) { - static::$userUrlMap[$user->guid] = $user->username; - return static::$userUrlMap[$user->guid]; - } - - return null; + static::$userUrlMap[$guid] = ($user !== null) ? $user->username : null; + return static::$userUrlMap[$guid]; } } diff --git a/protected/humhub/modules/user/controllers/AccountController.php b/protected/humhub/modules/user/controllers/AccountController.php index 3488897c1a..29e6499977 100644 --- a/protected/humhub/modules/user/controllers/AccountController.php +++ b/protected/humhub/modules/user/controllers/AccountController.php @@ -14,6 +14,8 @@ use humhub\modules\user\components\BaseAccountController; use humhub\modules\user\models\User; use humhub\modules\notification\models\forms\NotificationSettings; use humhub\modules\user\controllers\ImageController; +use humhub\modules\space\helpers\MembershipHelper; +use humhub\modules\user\models\forms\AccountDelete; /** * AccountController provides all standard actions for the current logged in @@ -257,37 +259,26 @@ class AccountController extends BaseAccountController /** * Delete Action - * - * Its only possible if the user is not owner of a workspace. */ public function actionDelete() { - - $isSpaceOwner = false; - $user = Yii::$app->user->getIdentity(); - if (!Yii::$app->user->canDeleteAccount()) { - throw new HttpException(500, 'Account deletion not allowed'); + throw new HttpException(500, 'Account deletion not allowed!'); } - foreach (\humhub\modules\space\models\Membership::GetUserSpaces() as $space) { - if ($space->isSpaceOwner($user->id)) { - $isSpaceOwner = true; - } + // Ensure user is not owner of a space + $ownSpaces = MembershipHelper::getOwnSpaces($this->user); + if (count($ownSpaces) !== 0) { + return $this->render('delete_spaceowner', ['ownSpaces' => $ownSpaces]); } - $model = new \humhub\modules\user\models\forms\AccountDelete; - - if (!$isSpaceOwner && $model->load(Yii::$app->request->post()) && $model->validate()) { - $user->delete(); + $model = new AccountDelete(['user' => $this->getUser()]); + if ($model->load(Yii::$app->request->post()) && $model->performDelete()) { Yii::$app->user->logout(); return $this->goHome(); } - return $this->render('delete', array( - 'model' => $model, - 'isSpaceOwner' => $isSpaceOwner - )); + return $this->render('delete', ['model' => $model]); } /** diff --git a/protected/humhub/modules/user/controllers/ProfileController.php b/protected/humhub/modules/user/controllers/ProfileController.php index 140405bed1..6c04616173 100644 --- a/protected/humhub/modules/user/controllers/ProfileController.php +++ b/protected/humhub/modules/user/controllers/ProfileController.php @@ -76,7 +76,6 @@ class ProfileController extends ContentContainerController public function actionHome() { - $this->hideSidebar = false; return $this->render('home', ['user' => $this->contentContainer]); } @@ -86,7 +85,6 @@ class ProfileController extends ContentContainerController throw new HttpException(403, 'Forbidden'); } - $this->hideSidebar = false; return $this->render('about', ['user' => $this->contentContainer]); } diff --git a/protected/humhub/modules/user/grid/BaseColumn.php b/protected/humhub/modules/user/grid/BaseColumn.php new file mode 100644 index 0000000000..3650bba9e8 --- /dev/null +++ b/protected/humhub/modules/user/grid/BaseColumn.php @@ -0,0 +1,44 @@ +userAttribute === null) { + return $record; + } + + $attributeName = $this->userAttribute; + return $record->$attributeName; + } + +} diff --git a/protected/humhub/modules/user/grid/DisplayNameColumn.php b/protected/humhub/modules/user/grid/DisplayNameColumn.php new file mode 100644 index 0000000000..5a6cd7fd3b --- /dev/null +++ b/protected/humhub/modules/user/grid/DisplayNameColumn.php @@ -0,0 +1,54 @@ +attribute === null) { + $this->attribute = 'profile.lastname'; + } + + if ($this->label === null) { + $this->label = Yii::t('UserModule.base', 'Name'); + } + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + $user = $this->getUser($model); + + + $badge = ''; + if ($user->auth_mode == 'ldap') { + $badge = ' LDAP'; + } + return '
    ' . Html::encode($user->displayName) . $badge . '
    ' . + '' . Html::encode($user->username) . '
    '; + } + +} diff --git a/protected/humhub/modules/user/grid/ImageColumn.php b/protected/humhub/modules/user/grid/ImageColumn.php new file mode 100644 index 0000000000..722637d7c2 --- /dev/null +++ b/protected/humhub/modules/user/grid/ImageColumn.php @@ -0,0 +1,39 @@ +options['style'] = 'width:38px'; + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + return UserImage::widget(['user' => $this->getUser($model), 'width' => 34]); + } + +} diff --git a/protected/humhub/modules/user/jobs/DeleteExpiredSessions.php b/protected/humhub/modules/user/jobs/DeleteExpiredSessions.php new file mode 100644 index 0000000000..1d9b3de8ce --- /dev/null +++ b/protected/humhub/modules/user/jobs/DeleteExpiredSessions.php @@ -0,0 +1,33 @@ +where(['<', 'expire', time()])->all() as $session) { + $session->delete(); + } + } + +} diff --git a/protected/humhub/modules/user/jobs/DeleteUser.php b/protected/humhub/modules/user/jobs/DeleteUser.php new file mode 100644 index 0000000000..07883098df --- /dev/null +++ b/protected/humhub/modules/user/jobs/DeleteUser.php @@ -0,0 +1,53 @@ +user_id)) { + throw new InvalidParamException('User id cannot be empty!'); + } + + return 'user.deleteUser.' . $this->user_id; + } + + /** + * @inhertidoc + */ + public function run() + { + $user = User::findOne(['id' => $this->user_id]); + if ($user === null) { + return; + } + + $user->delete(); + + } + +} diff --git a/protected/humhub/modules/user/jobs/SoftDeleteUser.php b/protected/humhub/modules/user/jobs/SoftDeleteUser.php new file mode 100644 index 0000000000..798f845ec5 --- /dev/null +++ b/protected/humhub/modules/user/jobs/SoftDeleteUser.php @@ -0,0 +1,53 @@ +user_id)) { + throw new InvalidParamException('User id cannot be empty!'); + } + + return 'user.softDeleteUser.' . $this->user_id; + } + + /** + * @inhertidoc + */ + public function run() + { + $user = User::findOne(['id' => $this->user_id]); + if ($user === null) { + return; + } + + $user->softDelete(); + } + +} diff --git a/protected/humhub/modules/user/jobs/SyncUsers.php b/protected/humhub/modules/user/jobs/SyncUsers.php new file mode 100644 index 0000000000..428e39cb89 --- /dev/null +++ b/protected/humhub/modules/user/jobs/SyncUsers.php @@ -0,0 +1,42 @@ +authClientCollection->getClients() as $authClient) { + if ($authClient instanceof AutoSyncUsers) { + /** + * @var AutoSyncUsers $authClient + */ + $authClient->syncUsers(); + } + } + } + +} diff --git a/protected/humhub/modules/user/migrations/m131213_165552_user_optimize.php b/protected/humhub/modules/user/migrations/m131213_165552_user_optimize.php index 688520290c..d6bccd6881 100644 --- a/protected/humhub/modules/user/migrations/m131213_165552_user_optimize.php +++ b/protected/humhub/modules/user/migrations/m131213_165552_user_optimize.php @@ -23,7 +23,8 @@ class m131213_165552_user_optimize extends Migration $this->createIndex('index_status', 'user_space_membership', 'status', false); - $this->createIndex('index_user_module', 'user_module', 'user_id, module_id', true); + # not longer required in 1.3 - disabled for utf8_mb4 support + #$this->createIndex('index_user_module', 'user_module', 'user_id, module_id', true); $this->createIndex('unique_token', 'user_invite', 'token', true); $this->createIndex('unique_email', 'user_invite', 'email', true); diff --git a/protected/humhub/modules/user/migrations/m140902_091234_session_key_length.php b/protected/humhub/modules/user/migrations/m140902_091234_session_key_length.php index 71b55470eb..c6605bdf27 100644 --- a/protected/humhub/modules/user/migrations/m140902_091234_session_key_length.php +++ b/protected/humhub/modules/user/migrations/m140902_091234_session_key_length.php @@ -8,7 +8,7 @@ class m140902_091234_session_key_length extends Migration public function up() { - $this->alterColumn('user_http_session', 'id', 'char(255) NOT NULL'); + $this->alterColumn('user_http_session', 'id', 'char(64) NOT NULL'); } public function down() diff --git a/protected/humhub/modules/user/migrations/m160229_162959_multiusergroups.php b/protected/humhub/modules/user/migrations/m160229_162959_multiusergroups.php index 9aa0f64220..558bb1592f 100644 --- a/protected/humhub/modules/user/migrations/m160229_162959_multiusergroups.php +++ b/protected/humhub/modules/user/migrations/m160229_162959_multiusergroups.php @@ -1,12 +1,15 @@ createTable('group_user', array( + $this->createTable('group_user', [ 'id' => 'pk', 'user_id' => 'int(11) NOT NULL', 'group_id' => 'int(11) NOT NULL', @@ -15,53 +18,53 @@ class m160229_162959_multiusergroups extends \humhub\components\Migration 'created_by' => 'int(11) DEFAULT NULL', 'updated_at' => 'datetime DEFAULT NULL', 'updated_by' => 'int(11) DEFAULT NULL', - ), ''); - - //Add indexes and foreign keys + ], ''); + + // Add indexes and foreign keys $this->createIndex('idx-group_user', 'group_user', ['user_id', 'group_id'], true); $this->addForeignKey('fk-user-group', 'group_user', 'user_id', 'user', 'id', 'CASCADE'); $this->addForeignKey('fk-group-group', 'group_user', 'group_id', '`group`', 'id', 'CASCADE'); - - //Merge old group user and group admins + + // Merge old group user and group admins $this->execute('INSERT INTO group_user (user_id, group_id) SELECT DISTINCT user.id, user.group_id FROM user LEFT JOIN `group` ON user.group_id=group.id WHERE group.id IS NOT NULL'); $this->execute('UPDATE group_user u SET is_group_admin = :value WHERE EXISTS (Select 1 FROM group_admin a WHERE u.user_id = a.user_id);', [':value' => 1]); - - //Add group columns + + // Add group columns $this->addColumn('group', 'is_admin_group', Schema::TYPE_BOOLEAN. ' NOT NULL DEFAULT 0'); $this->addColumn('group', 'show_at_registration', Schema::TYPE_BOOLEAN. ' NOT NULL DEFAULT 1'); $this->addColumn('group', 'show_at_directory', Schema::TYPE_BOOLEAN. ' NOT NULL DEFAULT 1'); - - //Create initial administration group + + // Create initial administration group $this->insertSilent('group', [ 'name' => 'Administrator', 'description' => 'Administrator Group', 'is_admin_group' => '1', 'show_at_registration' => '0', 'show_at_directory' => '0', - 'created_at' => new \yii\db\Expression('NOW()') + 'created_at' => new Expression('NOW()') ]); - - //Determine administration group id - $adminGroupId = (new \yii\db\Query()) + + // Determine administration group id + $adminGroupId = (new Query()) ->select('id') ->from('group') ->where(['is_admin_group' => '1']) ->scalar(); - - //Load current super_admin user - $rows = (new \yii\db\Query()) + + // Load current super_admin user + $rows = (new Query()) ->select("id") ->from('user') ->where(['super_admin' => '1']) ->all(); - - //Insert group_user for administartion groups for all current super_admins - foreach($rows as $adminUserRow) { + + // Insert group_user for administration groups for all current super_admins + foreach ($rows as $adminUserRow) { $this->insertSilent('group_user', ['user_id' => $adminUserRow['id'], 'group_id' => $adminGroupId, 'is_group_admin' => '1']); } - + //$this->insertSilent('group_permission', ['permission_id' => 'user_admin', 'group_id' => $adminGroupId, 'module_id' => 'user', 'class' => 'humhub\modules\user\permissions']); - + $this->dropTable('group_admin'); $this->dropColumn('user', 'super_admin'); $this->dropColumn('user', 'group_id'); diff --git a/protected/humhub/modules/user/migrations/m171025_200312_utf8mb4_fixes.php b/protected/humhub/modules/user/migrations/m171025_200312_utf8mb4_fixes.php new file mode 100644 index 0000000000..629d7e71f3 --- /dev/null +++ b/protected/humhub/modules/user/migrations/m171025_200312_utf8mb4_fixes.php @@ -0,0 +1,36 @@ +alterColumn('user_http_session', 'id', 'char(64) NOT NULL'); + $this->alterColumn('contentcontainer', 'guid', 'char(36) NOT NULL'); + $this->alterColumn('contentcontainer', 'class', 'char(60) NOT NULL'); + } + + public function safeDown() + { + echo "m171025_200312_utf8mb4_fixes cannot be reverted.\n"; + + return false; + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m171025_200312_utf8mb4_fixes cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/protected/humhub/modules/user/models/Module.php b/protected/humhub/modules/user/models/Module.php deleted file mode 100644 index eea47eb8dc..0000000000 --- a/protected/humhub/modules/user/models/Module.php +++ /dev/null @@ -1,131 +0,0 @@ - 255], - [['user_id', 'module_id'], 'unique', 'targetAttribute' => ['user_id', 'module_id'], 'message' => 'The combination of Module ID and User ID has already been taken.'] - ]; - } - - /** - * @inheritdoc - */ - public function attributeLabels() - { - return [ - 'id' => 'ID', - 'module_id' => 'Module ID', - 'user_id' => 'User ID', - 'state' => 'State', - ]; - } - - /** - * @inheritdoc - */ - public function beforeSave($insert) - { - Yii::$app->cache->delete(self::STATES_CACHE_ID_PREFIX . $this->user_id); - self::$_states[$this->user_id] = null; - return parent::beforeSave($insert); - } - - /** - * @inheritdoc - */ - public function beforeDelete() - { - Yii::$app->cache->delete(self::STATES_CACHE_ID_PREFIX . $this->user_id); - self::$_states[$this->user_id] = null; - return parent::beforeDelete(); - } - - /** - * Returns an array of moduleId and the their states (enabled, disabled, force enabled) - * for given user id. If space id is 0 or empty, the default states will be returned. - * - * @param int|null $userId or null for default states - * @return array State of Module Ids - */ - public static function getStates($userId = null) - { - if (isset(self::$_states[$userId])) { - return self::$_states[$userId]; - } - - $states = Yii::$app->cache->get(self::STATES_CACHE_ID_PREFIX . $userId); - if ($states === false) { - $states = []; - $query = self::find(); - - if (empty($userId)) { - $query->andWhere(['IS', 'user_id', new \yii\db\Expression('NULL')]); - } else { - $query->andWhere(['user_id' => $userId]); - } - - foreach ($query->all() as $userModule) { - $states[$userModule->module_id] = $userModule->state; - } - Yii::$app->cache->set(self::STATES_CACHE_ID_PREFIX . $userId, $states); - } - - self::$_states[$userId] = $states; - - return self::$_states[$userId]; - } - - /** - * Related user - * - * @return \yii\db\ActiveQuery - */ - public function getUser() - { - return $this->hasOne(User::className(), ['id' => 'user_id']); - } - -} diff --git a/protected/humhub/modules/user/models/Profile.php b/protected/humhub/modules/user/models/Profile.php index 6bda41f201..9f2bbe855f 100644 --- a/protected/humhub/modules/user/models/Profile.php +++ b/protected/humhub/modules/user/models/Profile.php @@ -44,6 +44,7 @@ use Yii; * @property string $url_myspace * @property string $url_googleplus * @property string $url_twitter + * @property User $user */ class Profile extends \yii\db\ActiveRecord { @@ -251,17 +252,14 @@ class Profile extends \yii\db\ActiveRecord /** * Returns all profile field categories with some user data * - * @todo Optimize me - * @return Array ProfileFieldCategory + * @return ProfileFieldCategory[] */ public function getProfileFieldCategories() { - - $categories = array(); + $categories = []; foreach (ProfileFieldCategory::find()->orderBy('sort_order')->all() as $category) { - - if (count($this->getProfileFields($category)) != 0) { + if (count($this->getProfileFields($category)) > 0) { $categories[] = $category; } } @@ -272,31 +270,51 @@ class Profile extends \yii\db\ActiveRecord /** * Returns all profile fields with user data by given category * - * @todo Optimize me * @param ProfileFieldCategory $category - * @return Array ProfileFields + * @return ProfileField[] */ public function getProfileFields(ProfileFieldCategory $category = null) { - if ($this->user === null) { - return []; - } - $fields = []; - $query = ProfileField::find(); - $query->where(['visible' => 1]); - $query->orderBy('sort_order'); - if ($category !== null) { - $query->andWhere(['profile_field_category_id' => $category->id]); - } - foreach ($query->all() as $field) { - if ($field->getUserValue($this->user) != "") { - $fields[] = $field; + if ($this->user !== null) { + $query = ProfileField::find() + ->where(['visible' => 1]) + ->orderBy('sort_order'); + + if ($category !== null) { + $query->andWhere(['profile_field_category_id' => $category->id]); + } + + /** @var ProfileField $profileFieldModels */ + $profileFieldModels = $query->all(); + + foreach ($profileFieldModels as $field) { + if (!empty($field->getUserValue($this->user))) { + $fields[] = $field; + } } } return $fields; } + /** + * Soft delete will empty all profile fields except these defined in the module configuration. + */ + public function softDelete() + { + $module = Yii::$app->getModule('user'); + /* @var $module \humhub\modules\user\Module */ + + foreach (array_keys($this->getAttributes()) as $name) { + if (!in_array($name, $module->softDeleteKeepProfileFields) && $name !== 'user_id') { + $this->setAttribute($name, ''); + } + } + if (!$this->save()) { + Yii::error('Could not soft delete profile!'); + } + } + } diff --git a/protected/humhub/modules/user/models/User.php b/protected/humhub/modules/user/models/User.php index dcc0b789ce..ce1d14687c 100644 --- a/protected/humhub/modules/user/models/User.php +++ b/protected/humhub/modules/user/models/User.php @@ -17,6 +17,7 @@ use humhub\modules\friendship\models\Friendship; use humhub\modules\space\models\Space; use humhub\modules\content\models\Content; use humhub\modules\space\models\Membership; +use humhub\modules\space\helpers\MembershipHelper; /** * This is the model class for table "user". @@ -49,6 +50,7 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte const STATUS_DISABLED = 0; const STATUS_ENABLED = 1; const STATUS_NEED_APPROVAL = 2; + const STATUS_SOFT_DELETED = 3; /** * Visibility Modes @@ -69,6 +71,11 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte */ const EVENT_CHECK_VISIBILITY = 'checkVisibility'; + /** + * @event UserEvent an event that is triggered when the user is soft deleted (without contents) and also before complete deletion. + */ + const EVENT_BEFORE_SOFT_DELETE = 'beforeSoftDelete'; + /** * A initial group for the user assigned while registration. * @var type @@ -80,6 +87,17 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte */ private $_isSystemAdmin = null; + /** + * @inheritdoc + */ + public $controllerBehavior = \humhub\modules\user\behaviors\ProfileController::class; + + /** + * + * @var type + */ + public $defaultRoute = '/user/profile'; + /** * @inheritdoc */ @@ -194,13 +212,16 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte ]; } + /** + * @inheritdoc + */ public function behaviors() { return array( - \humhub\components\behaviors\GUID::className(), - \humhub\modules\content\components\behaviors\SettingsBehavior::className(), - \humhub\modules\user\behaviors\Followable::className(), - \humhub\modules\user\behaviors\UserModelModules::className() + \humhub\components\behaviors\GUID::class, + \humhub\modules\content\components\behaviors\SettingsBehavior::class, + \humhub\modules\user\behaviors\Followable::class, + \humhub\modules\content\components\behaviors\CompatModuleManager::class ); } @@ -339,24 +360,38 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte */ public function beforeDelete() { - // We don't allow deletion of users who owns a space - validate that - foreach (Membership::GetUserSpaces($this->id, false) as $space) { - if ($space->isSpaceOwner($this->id)) { - throw new Exception('Tried to delete a user (' . $this->id . ') which is owner of a space (' . $space->id . ')!'); - } + $this->softDelete(); + + if ($this->profile !== null) { + $this->profile->delete(); } - // Disable all enabled modules - foreach ($this->getAvailableModules() as $moduleId => $module) { - if ($this->isModuleEnabled($moduleId)) { - $this->disableModule($moduleId); - } + return parent::beforeDelete(); + } + + /** + * + * @since 1.3 + * @throws Exception + */ + public function softDelete() + { + // Delete spaces which are owned by this user. + foreach (MembershipHelper::getOwnSpaces($this) as $space) { + $space->delete(); } - // Delete profile image + $this->trigger(self::EVENT_BEFORE_SOFT_DELETE, new UserEvent(['user' => $this])); + + if ($this->profile !== null) { + $this->profile->softDelete(); + } $this->getProfileImage()->delete(); + $this->getProfileBannerImage()->delete(); - // Remove from search index + foreach ($this->moduleManager->getEnabled() as $module) { + $this->moduleManager->disable($module); + } Yii::$app->search->delete($this); // Cleanup related tables @@ -364,11 +399,16 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte Follow::deleteAll(['user_id' => $this->id]); Follow::deleteAll(['object_model' => $this->className(), 'object_id' => $this->id]); Password::deleteAll(['user_id' => $this->id]); - Profile::deleteAll(['user_id' => $this->id]); GroupUser::deleteAll(['user_id' => $this->id]); Session::deleteAll(['user_id' => $this->id]); - return parent::beforeDelete(); + $this->updateAttributes([ + 'email' => new \yii\db\Expression('NULL'), + 'username' => 'deleted-' . $this->id, + 'status' => User::STATUS_SOFT_DELETED + ]); + + return true; } /** @@ -591,7 +631,7 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte if (!$this->profile->isNewRecord) { foreach ($this->profile->getProfileFields() as $profileField) { if ($profileField->searchable) { - $attributes['profile_' . $profileField->internal_name] = $profileField->getUserValue($this, true); + $attributes['profile_' . $profileField->internal_name] = $profileField->getUserValue($this, false); } } } @@ -601,20 +641,6 @@ class User extends ContentContainerActiveRecord implements \yii\web\IdentityInte return $attributes; } - public function createUrl($route = null, $params = [], $scheme = false) - { - if ($route === null) { - $route = '/user/profile'; - } - - array_unshift($params, $route); - if (!isset($params['uguid'])) { - $params['uguid'] = $this->guid; - } - - return \yii\helpers\Url::toRoute($params, $scheme); - } - /** * * @return type diff --git a/protected/humhub/modules/user/models/fieldtype/Birthday.php b/protected/humhub/modules/user/models/fieldtype/Birthday.php index ea9f9da08f..790e04e966 100644 --- a/protected/humhub/modules/user/models/fieldtype/Birthday.php +++ b/protected/humhub/modules/user/models/fieldtype/Birthday.php @@ -17,13 +17,24 @@ use Yii; * * @since 0.5 */ -class Birthday extends Date +class Birthday extends BaseType { /** - * @var boolean hide age per default + * The public property $defaultHideAge is configured by loadFieldConfig in BaseType and looks like an integer + * but is stored as string. The value for $hideAge (the user input) looks like an integer and is stored as integer. */ - public $defaultHideAge = false; + + const DEFAULT_HIDE_AGE_YES = '1'; + const DEFAULT_HIDE_AGE_NO = '0'; + + const HIDE_AGE_YES = 1; + const HIDE_AGE_NO = 0; + + /** + * @var string hide age by default + */ + public $defaultHideAge = self::DEFAULT_HIDE_AGE_NO; /** * @inheritdoc @@ -31,49 +42,62 @@ class Birthday extends Date public function rules() { return [ - [['defaultHideAge'], 'in', 'range' => array(0, 1)] + [['defaultHideAge'], 'in', 'range' => [self::DEFAULT_HIDE_AGE_NO, self::DEFAULT_HIDE_AGE_YES]] ]; } /** * @inheritdoc */ - public function getFormDefinition($definition = array()) + public function getFormDefinition($definition = []) { return parent::getFormDefinition([ - get_class($this) => [ - 'type' => 'form', - 'title' => Yii::t('UserModule.models_ProfileFieldTypeBirthday', 'Birthday field options'), - 'elements' => [ - 'defaultHideAge' => [ - 'type' => 'checkbox', - 'label' => Yii::t('UserModule.models_ProfileFieldTypeBirthday', 'Hide age per default'), - 'class' => 'form-control', - ], - ] - ] + get_class($this) => [ + 'type' => 'form', + 'title' => Yii::t('UserModule.models_ProfileFieldTypeBirthday', 'Birthday field options'), + 'elements' => [ + 'defaultHideAge' => [ + 'type' => 'checkbox', + 'label' => Yii::t('UserModule.models_ProfileFieldTypeBirthday', 'Hide age per default'), + 'class' => 'form-control', + ], + ] + ] ]); } + /** + * @inheritdoc + */ public function delete() { - // Try create column name - if (Profile::columnExists($this->profileField->internal_name)) { - $sql = "ALTER TABLE profile DROP `" . $this->profileField->internal_name . "_hide_year`;"; - Yii::$app->db->createCommand($sql)->execute(); + // Delete the birthdate_hide_year field + $columnNameHideYear = $this->profileField->internal_name . '_hide_year'; + if (Profile::columnExists($columnNameHideYear)) { + $query = Yii::$app->db->getQueryBuilder()->dropColumn(Profile::tableName(), $columnNameHideYear); + Yii::$app->db->createCommand($query)->execute(); } + // Delete the birthdate field (this is done by parent implementation) return parent::delete(); } /** - * Saves this Profile Field Type + * @inheritdoc */ public function save() { + // Add the birthdate_hide_year field + $columnNameHide = $this->profileField->internal_name . '_hide_year'; + if (!Profile::columnExists($columnNameHide)) { + $query = Yii::$app->db->getQueryBuilder()->addColumn(Profile::tableName(), $columnNameHide, 'INT(1)'); + Yii::$app->db->createCommand($query)->execute(); + } + + // Add the birthdate field $columnName = $this->profileField->internal_name; - if (!\humhub\modules\user\models\Profile::columnExists($columnName)) { - $query = Yii::$app->db->getQueryBuilder()->addColumn(\humhub\modules\user\models\Profile::tableName(), $columnName . '_hide_year', 'INT(1)'); + if (!Profile::columnExists($columnName)) { + $query = Yii::$app->db->getQueryBuilder()->addColumn(Profile::tableName(), $columnName, 'DATE'); Yii::$app->db->createCommand($query)->execute(); } @@ -81,50 +105,68 @@ class Birthday extends Date } /** - * Returns the Field Rules, to validate users input - * - * @param type $rules - * @return type + * @inheritdoc */ - public function getFieldRules($rules = array()) + public function getFieldRules($rules = []) { - - $rules[] = [$this->profileField->internal_name . "_hide_year", 'in', 'range' => [0, 1]]; - $rules[] = [$this->profileField->internal_name, + // Add validation for birthdate + $rules[] = [ + $this->profileField->internal_name, \humhub\libs\DbDateValidator::className(), - 'format' => Yii::$app->formatter->dateInputFormat, - 'convertToFormat' => null, + 'format' => 'medium', + 'convertToFormat' => 'Y-m-d', 'max' => time(), 'tooBig' => Yii::t('base', 'The date has to be in the past.') ]; + + // Add validation for birthdate_hide_year + $rules[] = [ + $this->profileField->internal_name . '_hide_year', + 'in', + 'range' => [self::HIDE_AGE_NO, self::HIDE_AGE_YES] + ]; + return parent::getFieldRules($rules); } /** - * Return the Form Element to edit the value of the Field + * @inheritdoc */ public function getFieldFormDefinition() { - return [$this->profileField->internal_name => [ + return [ + $this->profileField->internal_name => [ 'type' => 'datetime', - 'format' => Yii::$app->formatter->dateInputFormat, + 'format' => 'medium', 'class' => 'form-control', 'readonly' => (!$this->profileField->editable), - 'yearRange' => (date('Y') - 100) . ":" . date('Y') + 'yearRange' => (date('Y') - 100) . ':' . date('Y'), + 'dateTimePickerOptions' => [ + 'pickTime' => false + ] ], - $this->profileField->internal_name . "_hide_year" => [ + $this->profileField->internal_name . '_hide_year' => [ 'type' => 'checkbox', 'readonly' => (!$this->profileField->editable) ], ]; } + /** + * @inheritdoc + */ public function getLabels() { - $labels = array(); - $labels[$this->profileField->internal_name] = Yii::t($this->profileField->getTranslationCategory(), $this->profileField->title); - $labels[$this->profileField->internal_name . "_hide_year"] = Yii::t($this->profileField->getTranslationCategory(), "Hide year in profile"); - return $labels; + return [ + $this->profileField->internal_name => Yii::t( + $this->profileField->getTranslationCategory(), + $this->profileField->title + ), + $this->profileField->internal_name . '_hide_year' => Yii::t( + $this->profileField->getTranslationCategory(), + 'Hide year in profile' + ), + ]; } /** @@ -133,30 +175,51 @@ class Birthday extends Date public function getUserValue($user, $raw = true) { $internalName = $this->profileField->internal_name; - $birthdayDate = $user->profile->$internalName; - - if ($birthdayDate == "" || $birthdayDate == "0000-00-00") - return ""; - - $internalNameHideAge = $this->profileField->internal_name . "_hide_year"; + $birthdayDate = \DateTime::createFromFormat('Y-m-d', $user->profile->$internalName); + $internalNameHideAge = $this->profileField->internal_name . '_hide_year'; $hideAge = $user->profile->$internalNameHideAge; - if (($hideAge === null && !$this->defaultHideAge) || $hideAge === 0) { - $birthDate = new \DateTime($birthdayDate); - $lifeSpan = $birthDate->diff(new \DateTime()); - $age = Yii::t('UserModule.models_ProfileFieldTypeBirthday', '%y Years', array('%y' => $lifeSpan->format("%y"))); - return Yii::$app->formatter->asDate($birthdayDate, 'long') . " (" . $age . ")"; - } else { + /* + * the current value is invalid or empty + */ + if ($birthdayDate === false) { + return ''; + } + + /* + * when getUserValue is called but loadDefaults not $hideAge might be null + */ + if ($hideAge === null) { + if ($this->defaultHideAge === self::DEFAULT_HIDE_AGE_YES) { + $hideAge = self::HIDE_AGE_YES; + } else { + $hideAge = self::HIDE_AGE_NO; + } + } + + /* + * - user set hide age yes + */ + if ($hideAge === self::HIDE_AGE_YES) { return Yii::$app->formatter->asDate($birthdayDate, 'dd. MMMM'); } + + $ageInYears = Yii::t( + 'UserModule.models_ProfileFieldTypeBirthday', + '%y Years', + ['%y' => $birthdayDate->diff(new \DateTime())->y] + ); + + return Yii::$app->formatter->asDate($birthdayDate, 'long') . ' (' . $ageInYears . ')'; } /** * @inheritdoc */ - public function loadDefaults(\humhub\modules\user\models\Profile $profile) + public function loadDefaults(Profile $profile) { + //you may configure the default for hideAge as administrator. currently the global default is 0. $internalNameHideAge = $this->profileField->internal_name . '_hide_year'; if ($profile->$internalNameHideAge === null) { $profile->$internalNameHideAge = $this->defaultHideAge; @@ -164,5 +227,3 @@ class Birthday extends Date } } - -?> diff --git a/protected/humhub/modules/user/models/forms/AccountDelete.php b/protected/humhub/modules/user/models/forms/AccountDelete.php index a8170de6d0..fd55b395e5 100644 --- a/protected/humhub/modules/user/models/forms/AccountDelete.php +++ b/protected/humhub/modules/user/models/forms/AccountDelete.php @@ -2,21 +2,24 @@ /** * @link https://www.humhub.org/ - * @copyright Copyright (c) 2016 HumHub GmbH & Co. KG + * @copyright Copyright (c) 2017 HumHub GmbH & Co. KG * @license https://www.humhub.com/licences */ namespace humhub\modules\user\models\forms; use Yii; +use yii\base\Model; use humhub\modules\user\components\CheckPasswordValidator; +use humhub\modules\user\models\User; +use humhub\modules\user\jobs\SoftDeleteUser; /** * AccountDelete is the model for account deletion. * * @since 0.5 */ -class AccountDelete extends \yii\base\Model +class AccountDelete extends Model { /** @@ -24,6 +27,12 @@ class AccountDelete extends \yii\base\Model */ public $currentPassword; + /** + * @since 1.3 + * @var User the user + */ + public $user; + /** * @inheritdoc */ @@ -35,7 +44,7 @@ class AccountDelete extends \yii\base\Model return [ ['currentPassword', 'required'], - ['currentPassword', CheckPasswordValidator::className()], + ['currentPassword', CheckPasswordValidator::className(), 'user' => $this->user], ]; } @@ -45,8 +54,26 @@ class AccountDelete extends \yii\base\Model public function attributeLabels() { return array( - 'currentPassword' => Yii::t('UserModule.forms_AccountDeleteForm', 'Your password'), + 'currentPassword' => Yii::t('UserModule.password', 'Your password'), ); } + /** + * Perform user deletion + * @since 1.3 + */ + public function performDelete() + { + if (!$this->validate()) { + return false; + } + + $this->user->status = User::STATUS_DISABLED; + $this->user->save(); + + Yii::$app->queue->push(new SoftDeleteUser(['user_id' => $this->user->id])); + + return true; + } + } diff --git a/protected/humhub/modules/user/views/account/delete.php b/protected/humhub/modules/user/views/account/delete.php index c93f6fdc89..b4bb16b1c1 100644 --- a/protected/humhub/modules/user/views/account/delete.php +++ b/protected/humhub/modules/user/views/account/delete.php @@ -5,27 +5,26 @@ use humhub\widgets\DataSaved; use humhub\widgets\ActiveForm; ?> beginContent('@user/views/account/_userProfileLayout.php') ?> - - Please assign another owner or delete them.'); ?> + + +
    +
    +
    + + + +isAttributeRequired('currentPassword')): ?> + field($model, 'currentPassword')->passwordInput(['maxlength' => 45, 'placeholder' => Yii::t('UserModule.account', 'Enter your password to continue')]); ?> - All your published content will be removed! '); ?> -
    -
    - - - - isAttributeRequired('currentPassword')): ?> - field($model, 'currentPassword')->passwordInput(['maxlength' => 45, 'placeholder' => Yii::t('UserModule.views_account_delete', 'Enter your password to continue')])->label(false); ?> - - field($model, 'currentPassword')->hiddenInput()->label(false); ?> - - - 'btn btn-danger', 'data-ui-loader' => '')); ?> - - - - + field($model, 'currentPassword')->hiddenInput()->label(false); ?> + +
    + + 'btn btn-danger', 'data-ui-loader' => '']); ?> + + + endContent(); ?> diff --git a/protected/humhub/modules/user/views/account/delete_spaceowner.php b/protected/humhub/modules/user/views/account/delete_spaceowner.php new file mode 100644 index 0000000000..224d86dae9 --- /dev/null +++ b/protected/humhub/modules/user/views/account/delete_spaceowner.php @@ -0,0 +1,29 @@ + +beginContent('@user/views/account/_userProfileLayout.php') ?> + +
    +
    +
    + + +
    +
    + $space, 'width' => 38, 'link' => true]); ?> +
    +
    +

    + $space->getMemberships()->count()]); ?> +
    +
    + +
    + +
    +
    +endContent(); ?> \ No newline at end of file diff --git a/protected/humhub/modules/user/views/profile/_layout.php b/protected/humhub/modules/user/views/profile/_layout.php index 9e634c4633..2b82c291b0 100644 --- a/protected/humhub/modules/user/views/profile/_layout.php +++ b/protected/humhub/modules/user/views/profile/_layout.php @@ -1,38 +1,22 @@ context->getUser(); + +use humhub\modules\user\widgets\ProfileHeader; +use humhub\modules\user\widgets\ProfileMenu; + +$user = $this->context->contentContainer; ?>
    - $user]); ?> + $user]); ?>
    - $this->context->user]); ?> + $this->context->user]); ?> +
    +
    +
    - - context->hideSidebar) && $this->context->hideSidebar) : ?> -
    - -
    - -
    - -
    -
    - $this->context->user, - 'widgets' => [ - [\humhub\modules\user\widgets\UserTags::className(), ['user' => $this->context->user], ['sortOrder' => 10]], - [\humhub\modules\user\widgets\UserSpaces::className(), ['user' => $this->context->user], ['sortOrder' => 20]], - [\humhub\modules\friendship\widgets\FriendsPanel::className(), ['user' => $this->context->user], ['sortOrder' => 30]], - [\humhub\modules\user\widgets\UserFollower::className(), ['user' => $this->context->user], ['sortOrder' => 40]], - ] - ]); - ?> -
    -
    diff --git a/protected/humhub/modules/user/views/profile/_sidebar.php b/protected/humhub/modules/user/views/profile/_sidebar.php new file mode 100644 index 0000000000..42b513a855 --- /dev/null +++ b/protected/humhub/modules/user/views/profile/_sidebar.php @@ -0,0 +1,22 @@ + +
    +
    + +
    +
    + $user, + 'widgets' => [ + [\humhub\modules\user\widgets\UserTags::className(), ['user' => $user], ['sortOrder' => 10]], + [\humhub\modules\user\widgets\UserSpaces::className(), ['user' => $user], ['sortOrder' => 20]], + [\humhub\modules\friendship\widgets\FriendsPanel::className(), ['user' => $user], ['sortOrder' => 30]], + [\humhub\modules\user\widgets\UserFollower::className(), ['user' => $user], ['sortOrder' => 40]], + ] + ]); + ?> +
    +
    \ No newline at end of file diff --git a/protected/humhub/modules/user/views/profile/about.php b/protected/humhub/modules/user/views/profile/about.php index 619cc5a5e4..51adf489d9 100644 --- a/protected/humhub/modules/user/views/profile/about.php +++ b/protected/humhub/modules/user/views/profile/about.php @@ -4,6 +4,12 @@ use yii\helpers\Html; use humhub\widgets\RichText; use humhub\modules\user\models\fieldtype\MarkdownEditor; use humhub\widgets\MarkdownView; + +/** + * @var $this \humhub\components\View + * @var $user \humhub\modules\user\models\User + */ +$categories = $user->profile->getProfileFieldCategories(); ?>
    - profile->getProfileFieldCategories() as $category): ?> +
    $field->getUserValue($user, false)]); ?>

    -

    getUserValue($user, false); ?>

    +

    getUserValue($user, false); ?>

    diff --git a/protected/humhub/modules/user/views/profile/home.php b/protected/humhub/modules/user/views/profile/home.php index 8f79a547ea..87217d88cc 100644 --- a/protected/humhub/modules/user/views/profile/home.php +++ b/protected/humhub/modules/user/views/profile/home.php @@ -4,5 +4,9 @@ use humhub\modules\post\widgets\Form; use humhub\modules\user\widgets\StreamViewer; ?> +beginContent('@user/views/profile/_sidebar.php', ['user' => $user]); ?> + $user]); ?> $user]); ?> + +endContent(); ?> \ No newline at end of file diff --git a/protected/humhub/modules/user/widgets/Image.php b/protected/humhub/modules/user/widgets/Image.php index a1b738038c..8c3c410c86 100644 --- a/protected/humhub/modules/user/widgets/Image.php +++ b/protected/humhub/modules/user/widgets/Image.php @@ -11,6 +11,7 @@ namespace humhub\modules\user\widgets; use Yii; use humhub\libs\Html; use humhub\components\Widget; +use humhub\modules\user\models\User; /** * Image shows the user profile image @@ -83,6 +84,10 @@ class Image extends Widget */ public function run() { + if ($this->user->status == User::STATUS_SOFT_DELETED) { + $this->link = false; + } + Html::addCssClass($this->imageOptions, 'img-rounded'); Html::addCssStyle($this->imageOptions, 'width: ' . $this->width . 'px; height: ' . $this->height . 'px'); diff --git a/protected/humhub/widgets/CoreJsConfig.php b/protected/humhub/widgets/CoreJsConfig.php index d551623237..68faa428c1 100644 --- a/protected/humhub/widgets/CoreJsConfig.php +++ b/protected/humhub/widgets/CoreJsConfig.php @@ -36,17 +36,7 @@ class CoreJsConfig extends Widget [ 'user' => $userConfig, 'live' => [ - 'client' => [ - 'type' => 'humhub.modules.live.poll.PollClient', - 'options' => [ - 'url' => Url::to(['/live/poll']), - 'initTime' => time(), - 'minInterval' => $liveModule->minPollInterval, // Minimal polling request interval in seconds. - 'maxInterval' => $liveModule->maxPollInterval, // Maximal polling request interval in seconds. - 'idleFactor' => $liveModule->idleFactor, // Factor used in the actual interval calculation in case of user idle. - 'idleInterval' => $liveModule->idleInterval // Interval for updating the update delay in case of user idle in seconds. - ] - ] + 'client' => Yii::$app->live->driver->getJsConfig() ], 'client' => [ 'baseUrl' => Yii::$app->settings->get('baseUrl') diff --git a/static/js/humhub/humhub.ui.additions.js b/static/js/humhub/humhub.ui.additions.js index 6612915c5d..77987aae50 100644 --- a/static/js/humhub/humhub.ui.additions.js +++ b/static/js/humhub/humhub.ui.additions.js @@ -55,7 +55,7 @@ humhub.module('ui.additions', function (module, require, $) { if (options.filter && !options.filter.indexOf(id)) { return; } - + try { module.apply($element, id); } catch (e) { @@ -79,12 +79,12 @@ humhub.module('ui.additions', function (module, require, $) { } var $match = $element.find(addition.selector).addBack(addition.selector); - + // only apply addition if we actually find a match - if(!$match.length) { + if (!$match.length) { return; } - + addition.handler.apply($match, [$match, $element]); }; @@ -92,15 +92,35 @@ humhub.module('ui.additions', function (module, require, $) { event.on('humhub:ready', function (evt) { module.applyTo($('body')); }); - - require('action').registerHandler('copyToClipboard', function(evt) { - clipboard.writeText(evt.$target.text()).then(function() { + + require('action').registerHandler('copyToClipboard', function (evt) { + clipboard.writeText(evt.$target.text()).then(function () { require('ui.status').success(module.text('success.clipboard')); - }).catch(function(err) { + }).catch(function (err) { require('ui.status').error(module.text('error.clipboard'), true); }); }); + // Workaround: Bootstrap bug with dropdowns in responsive tables + // See: https://github.com/twbs/bootstrap/issues/11037 + $(document).on('shown.bs.dropdown', '.table-responsive', function (e) { + var t = $(this), + m = $(e.target).find('.dropdown-menu'), + tb = t.offset().top + t.height(), + mb = m.offset().top + m.outerHeight(true), + d = 20; // Space for shadow + scrollbar. + if (t[0].scrollWidth > t.innerWidth()) { + if (mb + d > tb) { + t.css('padding-bottom', ((mb + d) - tb)); + } + } else { + t.css('overflow', 'visible'); + } + }).on('hidden.bs.dropdown', '.table-responsive', function () { + $(this).css({'padding-bottom': '', 'overflow': ''}); + }); + + // workaround for jp-player since it sets display to inline which results in a broken view... $(document).on('click.humhub-jp-play', '.jp-play', function () { $(this).closest('.jp-controls').find('.jp-pause').css('display', 'block'); @@ -110,9 +130,9 @@ humhub.module('ui.additions', function (module, require, $) { module.register('autosize', '.autosize', function ($match) { $match.autosize(); }); - + module.register('select2', '[data-ui-select2]', function ($match) { - $match.select2({theme:"humhub"}); + $match.select2({theme: "humhub"}); }); // Show tooltips on elements @@ -142,7 +162,7 @@ humhub.module('ui.additions', function (module, require, $) { $match.loader(); }); }; - + var extend = function (id, handler, options) { options = options || {}; @@ -180,7 +200,7 @@ humhub.module('ui.additions', function (module, require, $) { // Jquery date picker div is not removed... $('#ui-datepicker-div').remove(); - + $('.popover').remove(); $('.tooltip').remove(); }; @@ -218,12 +238,12 @@ humhub.module('ui.additions', function (module, require, $) { var $node = $(node); node = $node[0]; var observer = new MutationObserver(function (mutations) { - mutations.forEach(function(mutation) { + mutations.forEach(function (mutation) { var $nodes = $(mutation.addedNodes).filter(function () { return this.nodeType === 1; // filter out text nodes }); - $nodes.each(function() { + $nodes.each(function () { var $this = $(this); module.applyTo($this); }) diff --git a/static/less/mixins.less b/static/less/mixins.less index 2d651eed3a..9a3146618b 100644 --- a/static/less/mixins.less +++ b/static/less/mixins.less @@ -4,8 +4,8 @@ // modules to adopt the colors from currently // activated theme) // -------------------------------------------------- -@import (reference) "../../protected/vendor/bower/fontawesome/less/font-awesome.less"; -@import (reference) "../../protected/vendor/bower/bootstrap/less/mixins.less"; +@import (reference) "../../protected/vendor/bower-asset/fontawesome/less/font-awesome.less"; +@import (reference) "../../protected/vendor/bower-asset/bootstrap/less/mixins.less"; /* Default */ diff --git a/static/less/variables.less b/static/less/variables.less index 14f3de94eb..01216651eb 100644 --- a/static/less/variables.less +++ b/static/less/variables.less @@ -1,8 +1,8 @@ // // Theme color variables // -------------------------------------------------- -@import (reference) "../../protected/vendor/bower/bootstrap/less/mixins.less"; -@import (reference) "../../protected/vendor/bower/fontawesome/less/variables.less"; +@import (reference) "../../protected/vendor/bower-asset/bootstrap/less/mixins.less"; +@import (reference) "../../protected/vendor/bower-asset/fontawesome/less/variables.less"; // // Colors for buttons status etc. diff --git a/static/resources/css/select2Theme/build.less b/static/resources/css/select2Theme/build.less index c9fc9243ff..dba985c358 100644 --- a/static/resources/css/select2Theme/build.less +++ b/static/resources/css/select2Theme/build.less @@ -1,3 +1,3 @@ -@import "../../../../protected/vendor/bower/bootstrap/less/variables"; -@import "../../../../protected/vendor/bower/bootstrap/less/mixins"; +@import "../../../../protected/vendor/bower-asset/bootstrap/less/variables"; +@import "../../../../protected/vendor/bower-asset/bootstrap/less/mixins"; @import "select2-humhub.less"; diff --git a/static/resources/js/tour/bootstrap-tour.min.css b/static/resources/js/tour/bootstrap-tour.min.css index f98810cc7e..4093d81e83 100644 --- a/static/resources/js/tour/bootstrap-tour.min.css +++ b/static/resources/js/tour/bootstrap-tour.min.css @@ -1,15 +1,15 @@ /* ======================================================================== - * bootstrap-tour - v0.10.1 + * bootstrap-tour - v0.11.0 * http://bootstraptour.com * ======================================================================== - * Copyright 2012-2013 Ulrich Sossou + * Copyright 2012-2015 Ulrich Sossou * * ======================================================================== - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the MIT License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://opensource.org/licenses/MIT * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,4 +19,4 @@ * ======================================================================== */ -.tour-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1100;background-color:#000;opacity:.8;filter:alpha(opacity=80)}.tour-step-backdrop{position:relative;z-index:1101;background:inherit}.tour-step-backdrop>td{position:relative;z-index:1101}.tour-step-background{position:absolute!important;z-index:1100;background:inherit;border-radius:6px}.popover[class*=tour-]{z-index:1100}.popover[class*=tour-] .popover-navigation{padding:9px 14px}.popover[class*=tour-] .popover-navigation [data-role=end]{float:right}.popover[class*=tour-] .popover-navigation [data-role=prev],.popover[class*=tour-] .popover-navigation [data-role=next],.popover[class*=tour-] .popover-navigation [data-role=end]{cursor:pointer}.popover[class*=tour-] .popover-navigation [data-role=prev].disabled,.popover[class*=tour-] .popover-navigation [data-role=next].disabled,.popover[class*=tour-] .popover-navigation [data-role=end].disabled{cursor:default}.popover[class*=tour-].orphan{position:fixed;margin-top:0}.popover[class*=tour-].orphan .arrow{display:none} \ No newline at end of file +.tour-backdrop{position:absolute;z-index:1100;background-color:#000;opacity:.8;filter:alpha(opacity=80)}.popover[class*=tour-]{z-index:1102}.popover[class*=tour-] .popover-navigation{padding:9px 14px;overflow:hidden}.popover[class*=tour-] .popover-navigation [data-role=end]{float:right}.popover[class*=tour-] .popover-navigation [data-role=prev],.popover[class*=tour-] .popover-navigation [data-role=next],.popover[class*=tour-] .popover-navigation [data-role=end]{cursor:pointer}.popover[class*=tour-] .popover-navigation [data-role=prev].disabled,.popover[class*=tour-] .popover-navigation [data-role=next].disabled,.popover[class*=tour-] .popover-navigation [data-role=end].disabled{cursor:default}.popover[class*=tour-].orphan{position:fixed;margin-top:0}.popover[class*=tour-].orphan .arrow{display:none} diff --git a/static/resources/js/tour/bootstrap-tour.min.js b/static/resources/js/tour/bootstrap-tour.min.js index bb0b6be6ba..1d064e60b8 100644 --- a/static/resources/js/tour/bootstrap-tour.min.js +++ b/static/resources/js/tour/bootstrap-tour.min.js @@ -1,15 +1,15 @@ /* ======================================================================== - * bootstrap-tour - v0.10.1 + * bootstrap-tour - v0.11.0 * http://bootstraptour.com * ======================================================================== - * Copyright 2012-2013 Ulrich Sossou + * Copyright 2012-2015 Ulrich Sossou * * ======================================================================== - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the MIT License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://opensource.org/licenses/MIT * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,4 +19,4 @@ * ======================================================================== */ -!function(t,e){var o,n;return n=e.document,o=function(){function o(o){var n;try{n=e.localStorage}catch(i){n=!1}this._options=t.extend({name:"tour",steps:[],container:"body",autoscroll:!0,keyboard:!0,storage:n,debug:!1,backdrop:!1,backdropPadding:0,redirect:!0,orphan:!1,duration:!1,delay:!1,basePath:"",template:'',afterSetState:function(){},afterGetState:function(){},afterRemoveState:function(){},onStart:function(){},onEnd:function(){},onShow:function(){},onShown:function(){},onHide:function(){},onHidden:function(){},onNext:function(){},onPrev:function(){},onPause:function(){},onResume:function(){}},o),this._force=!1,this._inited=!1,this.backdrop={overlay:null,$element:null,$background:null,backgroundShown:!1,overlayElementShown:!1}}return o.prototype.addSteps=function(t){var e,o,n;for(o=0,n=t.length;n>o;o++)e=t[o],this.addStep(e);return this},o.prototype.addStep=function(t){return this._options.steps.push(t),this},o.prototype.getStep=function(e){return null!=this._options.steps[e]?t.extend({id:"step-"+e,path:"",placement:"right",title:"",content:"

    ",next:e===this._options.steps.length-1?-1:e+1,prev:e-1,animation:!0,container:this._options.container,autoscroll:this._options.autoscroll,backdrop:this._options.backdrop,backdropPadding:this._options.backdropPadding,redirect:this._options.redirect,orphan:this._options.orphan,duration:this._options.duration,delay:this._options.delay,template:this._options.template,onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._options.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPrev:this._options.onPrev,onPause:this._options.onPause,onResume:this._options.onResume},this._options.steps[e]):void 0},o.prototype.init=function(t){return this._force=t,this.ended()?(this._debug("Tour ended, init prevented."),this):(this.setCurrentStep(),this._initMouseNavigation(),this._initKeyboardNavigation(),this._onResize(function(t){return function(){return t.showStep(t._current)}}(this)),null!==this._current&&this.showStep(this._current),this._inited=!0,this)},o.prototype.start=function(t){var e;return null==t&&(t=!1),this._inited||this.init(t),null===this._current&&(e=this._makePromise(null!=this._options.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(e,this.showStep,0)),this},o.prototype.next=function(){var t;return t=this.hideStep(this._current),this._callOnPromiseDone(t,this._showNextStep)},o.prototype.prev=function(){var t;return t=this.hideStep(this._current),this._callOnPromiseDone(t,this._showPrevStep)},o.prototype.goTo=function(t){var e;return e=this.hideStep(this._current),this._callOnPromiseDone(e,this.showStep,t)},o.prototype.end=function(){var o,i;return o=function(o){return function(){return t(n).off("click.tour-"+o._options.name),t(n).off("keyup.tour-"+o._options.name),t(e).off("resize.tour-"+o._options.name),o._setState("end","yes"),o._inited=!1,o._force=!1,o._clearTimer(),null!=o._options.onEnd?o._options.onEnd(o):void 0}}(this),i=this.hideStep(this._current),this._callOnPromiseDone(i,o)},o.prototype.ended=function(){return!this._force&&!!this._getState("end")},o.prototype.restart=function(){return this._removeState("current_step"),this._removeState("end"),this.start()},o.prototype.pause=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!0,this._duration-=(new Date).getTime()-this._start,e.clearTimeout(this._timer),this._debug("Paused/Stopped step "+(this._current+1)+" timer ("+this._duration+" remaining)."),null!=t.onPause?t.onPause(this,this._duration):void 0):this},o.prototype.resume=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!1,this._start=(new Date).getTime(),this._duration=this._duration||t.duration,this._timer=e.setTimeout(function(t){return function(){return t._isLast()?t.next():t.end()}}(this),this._duration),this._debug("Started step "+(this._current+1)+" timer with duration "+this._duration),null!=t.onResume&&this._duration!==t.duration?t.onResume(this,this._duration):void 0):this},o.prototype.hideStep=function(e){var o,n,i;return(i=this.getStep(e))?(this._clearTimer(),n=this._makePromise(null!=i.onHide?i.onHide(this,e):void 0),o=function(o){return function(){var n;return n=t(i.element),n.data("bs.popover")||n.data("popover")||(n=t("body")),n.popover("destroy").removeClass("tour-"+o._options.name+"-element tour-"+o._options.name+"-"+e+"-element"),i.reflex&&n.removeClass("tour-step-element-reflex").off(""+o._reflexEvent(i.reflex)+".tour-"+o._options.name),i.backdrop&&o._hideBackdrop(),null!=i.onHidden?i.onHidden(o):void 0}}(this),this._callOnPromiseDone(n,o),n):void 0},o.prototype.showStep=function(t){var o,i,r,s;return this.ended()?(this._debug("Tour ended, showStep prevented."),this):(s=this.getStep(t))?(r=t").parent().html()},o.prototype._reflexEvent=function(t){return"[object Boolean]"==={}.toString.call(t)?"click":t},o.prototype._reposition=function(e,o){var i,r,s,a,p,u,h;if(a=e[0].offsetWidth,r=e[0].offsetHeight,h=e.offset(),p=h.left,u=h.top,i=t(n).outerHeight()-h.top-e.outerHeight(),0>i&&(h.top=h.top+i),s=t("html").outerWidth()-h.left-e.outerWidth(),0>s&&(h.left=h.left+s),h.top<0&&(h.top=0),h.left<0&&(h.left=0),e.offset(h),"bottom"===o.placement||"top"===o.placement){if(p!==h.left)return this._replaceArrow(e,2*(h.left-p),a,"left")}else if(u!==h.top)return this._replaceArrow(e,2*(h.top-u),r,"top")},o.prototype._center=function(o){return o.css("top",t(e).outerHeight()/2-o.outerHeight()/2)},o.prototype._replaceArrow=function(t,e,o,n){return t.find(".arrow").css(n,e?50*(1-e/o)+"%":"")},o.prototype._scrollIntoView=function(o,n){var i,r,s,a,p,u;return i=t(o),i.length?(r=t(e),a=i.offset().top,u=r.height(),p=Math.max(0,a-u/2),this._debug("Scroll into view. ScrollTop: "+p+". Element offset: "+a+". Window height: "+u+"."),s=0,t("body, html").stop(!0,!0).animate({scrollTop:Math.ceil(p)},function(t){return function(){return 2===++s?(n(),t._debug("Scroll into view.\nAnimation end element offset: "+i.offset().top+".\nWindow height: "+r.height()+".")):void 0}}(this))):n()},o.prototype._onResize=function(o,n){return t(e).on("resize.tour-"+this._options.name,function(){return clearTimeout(n),n=setTimeout(o,100)})},o.prototype._initMouseNavigation=function(){var e;return e=this,t(n).off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']").on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']",function(t){return function(e){return e.preventDefault(),t.next()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']",function(t){return function(e){return e.preventDefault(),t.prev()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']",function(t){return function(e){return e.preventDefault(),t.end()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']",function(o){var n;return o.preventDefault(),n=t(this),n.text(e._paused?n.data("pause-text"):n.data("resume-text")),e._paused?e.resume():e.pause()})},o.prototype._initKeyboardNavigation=function(){return this._options.keyboard?t(n).on("keyup.tour-"+this._options.name,function(t){return function(e){if(e.which)switch(e.which){case 39:return e.preventDefault(),t._isLast()?t.next():t.end();case 37:if(e.preventDefault(),t._current>0)return t.prev();break;case 27:return e.preventDefault(),t.end()}}}(this)):void 0},o.prototype._makePromise=function(e){return e&&t.isFunction(e.then)?e:null},o.prototype._callOnPromiseDone=function(t,e,o){return t?t.then(function(t){return function(){return e.call(t,o)}}(this)):e.call(this,o)},o.prototype._showBackdrop=function(){return this.backdrop.backgroundShown?void 0:(this.backdrop=t("
    ",{"class":"tour-backdrop"}),this.backdrop.backgroundShown=!0,t("body").append(this.backdrop))},o.prototype._hideBackdrop=function(){return this._hideOverlayElement(),this._hideBackground()},o.prototype._hideBackground=function(){return this.backdrop?(this.backdrop.remove(),this.backdrop.overlay=null,this.backdrop.backgroundShown=!1):void 0},o.prototype._showOverlayElement=function(e){var o,n;return o=t(e.element),o&&0!==o.length&&!this.backdrop.overlayElementShown?(this.backdrop.overlayElementShown=!0,this.backdrop.$element=o.addClass("tour-step-backdrop"),this.backdrop.$background=t("
    ",{"class":"tour-step-background"}),n={width:o.innerWidth(),height:o.innerHeight(),offset:o.offset()},this.backdrop.$background.appendTo("body"),e.backdropPadding&&(n=this._applyBackdropPadding(e.backdropPadding,n)),this.backdrop.$background.width(n.width).height(n.height).offset(n.offset)):void 0},o.prototype._hideOverlayElement=function(){return this.backdrop.overlayElementShown?(this.backdrop.$element.removeClass("tour-step-backdrop"),this.backdrop.$background.remove(),this.backdrop.$element=null,this.backdrop.$background=null,this.backdrop.overlayElementShown=!1):void 0},o.prototype._applyBackdropPadding=function(t,e){return"object"==typeof t?(null==t.top&&(t.top=0),null==t.right&&(t.right=0),null==t.bottom&&(t.bottom=0),null==t.left&&(t.left=0),e.offset.top=e.offset.top-t.top,e.offset.left=e.offset.left-t.left,e.width=e.width+t.left+t.right,e.height=e.height+t.top+t.bottom):(e.offset.top=e.offset.top-t,e.offset.left=e.offset.left-t,e.width=e.width+2*t,e.height=e.height+2*t),e},o.prototype._clearTimer=function(){return e.clearTimeout(this._timer),this._timer=null,this._duration=null},o}(),e.Tour=o}(jQuery,window); \ No newline at end of file +!function(t,e){return"function"==typeof define&&define.amd?define(["jquery"],function(o){return t.Tour=e(o)}):"object"==typeof exports?module.exports=e(require("jquery")):t.Tour=e(t.jQuery)}(window,function(t){var e,o;return o=window.document,e=function(){function e(e){var o;try{o=window.localStorage}catch(n){o=!1}this._options=t.extend({name:"tour",steps:[],container:"body",autoscroll:!0,keyboard:!0,storage:o,debug:!1,backdrop:!1,backdropContainer:"body",backdropPadding:0,redirect:!0,orphan:!1,duration:!1,delay:!1,basePath:"",template:'',afterSetState:function(){},afterGetState:function(){},afterRemoveState:function(){},onStart:function(){},onEnd:function(){},onShow:function(){},onShown:function(){},onHide:function(){},onHidden:function(){},onNext:function(){},onPrev:function(){},onPause:function(){},onResume:function(){},onRedirectError:function(){}},e),this._force=!1,this._inited=!1,this._current=null,this.backdrops=[]}return e.prototype.addSteps=function(t){var e,o,n;for(o=0,n=t.length;n>o;o++)e=t[o],this.addStep(e);return this},e.prototype.addStep=function(t){return this._options.steps.push(t),this},e.prototype.getStep=function(e){return null!=this._options.steps[e]?t.extend({id:"step-"+e,path:"",host:"",placement:"right",title:"",content:"

    ",next:e===this._options.steps.length-1?-1:e+1,prev:e-1,animation:!0,container:this._options.container,autoscroll:this._options.autoscroll,backdrop:this._options.backdrop,backdropContainer:this._options.backdropContainer,backdropPadding:this._options.backdropPadding,redirect:this._options.redirect,reflexElement:this._options.steps[e].element,backdropElement:this._options.steps[e].element,orphan:this._options.orphan,duration:this._options.duration,delay:this._options.delay,template:this._options.template,onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._options.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPrev:this._options.onPrev,onPause:this._options.onPause,onResume:this._options.onResume,onRedirectError:this._options.onRedirectError},this._options.steps[e]):void 0},e.prototype.init=function(t){return this._force=t,this.ended()?(this._debug("Tour ended, init prevented."),this):(this.setCurrentStep(),this._initMouseNavigation(),this._initKeyboardNavigation(),this._onResize(function(t){return function(){return t.showStep(t._current)}}(this)),null!==this._current&&this.showStep(this._current),this._inited=!0,this)},e.prototype.start=function(t){var e;return null==t&&(t=!1),this._inited||this.init(t),null===this._current&&(e=this._makePromise(null!=this._options.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(e,this.showStep,0)),this},e.prototype.next=function(){var t;return t=this.hideStep(this._current,this._current+1),this._callOnPromiseDone(t,this._showNextStep)},e.prototype.prev=function(){var t;return t=this.hideStep(this._current,this._current-1),this._callOnPromiseDone(t,this._showPrevStep)},e.prototype.goTo=function(t){var e;return e=this.hideStep(this._current,t),this._callOnPromiseDone(e,this.showStep,t)},e.prototype.end=function(){var e,n;return e=function(e){return function(){return t(o).off("click.tour-"+e._options.name),t(o).off("keyup.tour-"+e._options.name),t(window).off("resize.tour-"+e._options.name),e._setState("end","yes"),e._inited=!1,e._force=!1,e._clearTimer(),null!=e._options.onEnd?e._options.onEnd(e):void 0}}(this),n=this.hideStep(this._current),this._callOnPromiseDone(n,e)},e.prototype.ended=function(){return!this._force&&!!this._getState("end")},e.prototype.restart=function(){return this._removeState("current_step"),this._removeState("end"),this._removeState("redirect_to"),this.start()},e.prototype.pause=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!0,this._duration-=(new Date).getTime()-this._start,window.clearTimeout(this._timer),this._debug("Paused/Stopped step "+(this._current+1)+" timer ("+this._duration+" remaining)."),null!=t.onPause?t.onPause(this,this._duration):void 0):this},e.prototype.resume=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!1,this._start=(new Date).getTime(),this._duration=this._duration||t.duration,this._timer=window.setTimeout(function(t){return function(){return t._isLast()?t.next():t.end()}}(this),this._duration),this._debug("Started step "+(this._current+1)+" timer with duration "+this._duration),null!=t.onResume&&this._duration!==t.duration?t.onResume(this,this._duration):void 0):this},e.prototype.hideStep=function(e,o){var n,i,r,s;return(s=this.getStep(e))?(this._clearTimer(),r=this._makePromise(null!=s.onHide?s.onHide(this,e):void 0),i=function(n){return function(){var i,r;return i=t(s.element),i.data("bs.popover")||i.data("popover")||(i=t("body")),i.popover("destroy").removeClass("tour-"+n._options.name+"-element tour-"+n._options.name+"-"+e+"-element").removeData("bs.popover").focus(),s.reflex&&t(s.reflexElement).removeClass("tour-step-element-reflex").off(""+n._reflexEvent(s.reflex)+".tour-"+n._options.name),s.backdrop&&(r=null!=o&&n.getStep(o),r&&r.backdrop&&r.backdropElement===s.backdropElement||n._hideOverlayElement(s)),null!=s.onHidden?s.onHidden(n):void 0}}(this),n=s.delay.hide||s.delay,"[object Number]"==={}.toString.call(n)&&n>0?(this._debug("Wait "+n+" milliseconds to hide the step "+(this._current+1)),window.setTimeout(function(t){return function(){return t._callOnPromiseDone(r,i)}}(this),n)):this._callOnPromiseDone(r,i),r):void 0},e.prototype.showStep=function(t){var e,n,i,r,s,a;return this.ended()?(this._debug("Tour ended, showStep prevented."),this):(a=this.getStep(t),a&&(s=t0?(this._debug("Wait "+i+" milliseconds to show the step "+(this._current+1)),window.setTimeout(function(t){return function(){return t._callOnPromiseDone(n,r)}}(this),i)):this._callOnPromiseDone(n,r),n):void 0)},e.prototype.getCurrentStep=function(){return this._current},e.prototype.setCurrentStep=function(t){return null!=t?(this._current=t,this._setState("current_step",t)):(this._current=this._getState("current_step"),this._current=null===this._current?null:parseInt(this._current,10)),this},e.prototype.redraw=function(){return this._showOverlayElement(this.getStep(this.getCurrentStep()))},e.prototype._setState=function(t,e){var o,n;if(this._options.storage){n=""+this._options.name+"_"+t;try{this._options.storage.setItem(n,e)}catch(i){o=i,o.code===DOMException.QUOTA_EXCEEDED_ERR&&this._debug("LocalStorage quota exceeded. State storage failed.")}return this._options.afterSetState(n,e)}return null==this._state&&(this._state={}),this._state[t]=e},e.prototype._removeState=function(t){var e;return this._options.storage?(e=""+this._options.name+"_"+t,this._options.storage.removeItem(e),this._options.afterRemoveState(e)):null!=this._state?delete this._state[t]:void 0},e.prototype._getState=function(t){var e,o;return this._options.storage?(e=""+this._options.name+"_"+t,o=this._options.storage.getItem(e)):null!=this._state&&(o=this._state[t]),(void 0===o||"null"===o)&&(o=null),this._options.afterGetState(t,o),o},e.prototype._showNextStep=function(){var t,e,o;return o=this.getStep(this._current),e=function(t){return function(){return t.showStep(o.next)}}(this),t=this._makePromise(null!=o.onNext?o.onNext(this):void 0),this._callOnPromiseDone(t,e)},e.prototype._showPrevStep=function(){var t,e,o;return o=this.getStep(this._current),e=function(t){return function(){return t.showStep(o.prev)}}(this),t=this._makePromise(null!=o.onPrev?o.onPrev(this):void 0),this._callOnPromiseDone(t,e)},e.prototype._debug=function(t){return this._options.debug?window.console.log("Bootstrap Tour '"+this._options.name+"' | "+t):void 0},e.prototype._isRedirect=function(t,e,o){var n;return null!=t&&""!==t&&("[object RegExp]"==={}.toString.call(t)&&!t.test(o.origin)||"[object String]"==={}.toString.call(t)&&this._isHostDifferent(t,o))?!0:(n=[o.pathname,o.search,o.hash].join(""),null!=e&&""!==e&&("[object RegExp]"==={}.toString.call(e)&&!e.test(n)||"[object String]"==={}.toString.call(e)&&this._isPathDifferent(e,n)))},e.prototype._isHostDifferent=function(t,e){switch({}.toString.call(t)){case"[object RegExp]":return!t.test(e.origin);case"[object String]":return this._getProtocol(t)!==this._getProtocol(e.href)||this._getHost(t)!==this._getHost(e.href);default:return!0}},e.prototype._isPathDifferent=function(t,e){return this._getPath(t)!==this._getPath(e)||!this._equal(this._getQuery(t),this._getQuery(e))||!this._equal(this._getHash(t),this._getHash(e))},e.prototype._isJustPathHashDifferent=function(t,e,o){var n;return null!=t&&""!==t&&this._isHostDifferent(t,o)?!1:(n=[o.pathname,o.search,o.hash].join(""),"[object String]"==={}.toString.call(e)?this._getPath(e)===this._getPath(n)&&this._equal(this._getQuery(e),this._getQuery(n))&&!this._equal(this._getHash(e),this._getHash(n)):!1)},e.prototype._redirect=function(e,n,i){var r;return t.isFunction(e.redirect)?e.redirect.call(this,i):(r="[object String]"==={}.toString.call(e.host)?""+e.host+i:i,this._debug("Redirect to "+r),this._getState("redirect_to")!==""+n?(this._setState("redirect_to",""+n),o.location.href=r):(this._debug("Error redirection loop to "+i),this._removeState("redirect_to"),null!=e.onRedirectError?e.onRedirectError(this):void 0))},e.prototype._isOrphan=function(e){return null==e.element||!t(e.element).length||t(e.element).is(":hidden")&&"http://www.w3.org/2000/svg"!==t(e.element)[0].namespaceURI},e.prototype._isLast=function(){return this._current").parent().html()},e.prototype._reflexEvent=function(t){return"[object Boolean]"==={}.toString.call(t)?"click":t},e.prototype._focus=function(t,e,o){var n,i;return i=o?"end":"next",n=t.find("[data-role='"+i+"']"),e.on("shown.bs.popover",function(){return n.focus()})},e.prototype._reposition=function(e,n){var i,r,s,a,u,p,h;if(a=e[0].offsetWidth,r=e[0].offsetHeight,h=e.offset(),u=h.left,p=h.top,i=t(o).outerHeight()-h.top-e.outerHeight(),0>i&&(h.top=h.top+i),s=t("html").outerWidth()-h.left-e.outerWidth(),0>s&&(h.left=h.left+s),h.top<0&&(h.top=0),h.left<0&&(h.left=0),e.offset(h),"bottom"===n.placement||"top"===n.placement){if(u!==h.left)return this._replaceArrow(e,2*(h.left-u),a,"left")}else if(p!==h.top)return this._replaceArrow(e,2*(h.top-p),r,"top")},e.prototype._center=function(e){return e.css("top",t(window).outerHeight()/2-e.outerHeight()/2)},e.prototype._replaceArrow=function(t,e,o,n){return t.find(".arrow").css(n,e?50*(1-e/o)+"%":"")},e.prototype._scrollIntoView=function(e,o){var n,i,r,s,a,u,p;if(n=t(e.element),!n.length)return o();switch(i=t(window),a=n.offset().top,s=n.outerHeight(),p=i.height(),u=0,e.placement){case"top":u=Math.max(0,a-p/2);break;case"left":case"right":u=Math.max(0,a+s/2-p/2);break;case"bottom":u=Math.max(0,a+s-p/2)}return this._debug("Scroll into view. ScrollTop: "+u+". Element offset: "+a+". Window height: "+p+"."),r=0,t("body, html").stop(!0,!0).animate({scrollTop:Math.ceil(u)},function(t){return function(){return 2===++r?(o(),t._debug("Scroll into view.\nAnimation end element offset: "+n.offset().top+".\nWindow height: "+i.height()+".")):void 0}}(this))},e.prototype._onResize=function(e,o){return t(window).on("resize.tour-"+this._options.name,function(){return clearTimeout(o),o=setTimeout(e,100)})},e.prototype._initMouseNavigation=function(){var e;return e=this,t(o).off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']").on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']",function(t){return function(e){return e.preventDefault(),t.next()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']",function(t){return function(e){return e.preventDefault(),t._current>0?t.prev():void 0}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']",function(t){return function(e){return e.preventDefault(),t.end()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']",function(o){var n;return o.preventDefault(),n=t(this),n.text(e._paused?n.data("pause-text"):n.data("resume-text")),e._paused?e.resume():e.pause()})},e.prototype._initKeyboardNavigation=function(){return this._options.keyboard?t(o).on("keyup.tour-"+this._options.name,function(t){return function(e){if(e.which)switch(e.which){case 39:return e.preventDefault(),t._isLast()?t.next():t.end();case 37:if(e.preventDefault(),t._current>0)return t.prev()}}}(this)):void 0},e.prototype._makePromise=function(e){return e&&t.isFunction(e.then)?e:null},e.prototype._callOnPromiseDone=function(t,e,o){return t?t.then(function(t){return function(){return e.call(t,o)}}(this)):e.call(this,o)},e.prototype._showBackground=function(e,n){var i,r,s,a,u,p,h,l,c;for(r=t(o).height(),a=t(o).width(),l=["top","bottom","left","right"],c=[],p=0,h=l.length;h>p;p++)switch(s=l[p],i=null!=(u=this.backdrops)[s]?u[s]:u[s]=t("
    ",{"class":"tour-backdrop "+s}),t(e.backdropContainer).append(i),s){case"top":c.push(i.height(n.offset.top>0?n.offset.top:0).width(a).offset({top:0,left:0}));break;case"bottom":c.push(i.offset({top:n.offset.top+n.height,left:0}).height(r-(n.offset.top+n.height)).width(a));break;case"left":c.push(i.offset({top:n.offset.top,left:0}).height(n.height).width(n.offset.left>0?n.offset.left:0));break;case"right":c.push(i.offset({top:n.offset.top,left:n.offset.left+n.width}).height(n.height).width(a-(n.offset.left+n.width)));break;default:c.push(void 0)}return c},e.prototype._showOverlayElement=function(e){var o,n;return o=t(e.backdropElement),0===o.length?n={width:0,height:0,offset:{top:0,left:0}}:(n={width:o.innerWidth(),height:o.innerHeight(),offset:o.offset()},o.addClass("tour-step-backdrop"),e.backdropPadding&&(n=this._applyBackdropPadding(e.backdropPadding,n))),this._showBackground(e,n)},e.prototype._hideOverlayElement=function(e){var o,n,i;t(e.backdropElement).removeClass("tour-step-backdrop"),i=this.backdrops;for(n in i)o=i[n],o.remove();return this.backdrops=[]},e.prototype._applyBackdropPadding=function(t,e){return"object"==typeof t?(null==t.top&&(t.top=0),null==t.right&&(t.right=0),null==t.bottom&&(t.bottom=0),null==t.left&&(t.left=0),e.offset.top=e.offset.top-t.top,e.offset.left=e.offset.left-t.left,e.width=e.width+t.left+t.right,e.height=e.height+t.top+t.bottom):(e.offset.top=e.offset.top-t,e.offset.left=e.offset.left-t,e.width=e.width+2*t,e.height=e.height+2*t),e},e.prototype._clearTimer=function(){return window.clearTimeout(this._timer),this._timer=null,this._duration=null},e.prototype._getProtocol=function(t){return t=t.split("://"),t.length>1?t[0]:"http"},e.prototype._getHost=function(t){return t=t.split("//"),t=t.length>1?t[1]:t[0],t.split("/")[0]},e.prototype._getPath=function(t){return t.replace(/\/?$/,"").split("?")[0].split("#")[0]},e.prototype._getQuery=function(t){return this._getParams(t,"?")},e.prototype._getHash=function(t){return this._getParams(t,"#")},e.prototype._getParams=function(t,e){var o,n,i,r,s;if(n=t.split(e),1===n.length)return{};for(n=n[1].split("&"),i={},r=0,s=n.length;s>r;r++)o=n[r],o=o.split("="),i[o[0]]=o[1]||"";return i},e.prototype._equal=function(t,e){var o,n,i,r,s,a;if("[object Object]"==={}.toString.call(t)&&"[object Object]"==={}.toString.call(e)){if(n=Object.keys(t),i=Object.keys(e),n.length!==i.length)return!1;for(o in t)if(r=t[o],!this._equal(e[o],r))return!1;return!0}if("[object Array]"==={}.toString.call(t)&&"[object Array]"==={}.toString.call(e)){if(t.length!==e.length)return!1;for(o=s=0,a=t.length;a>s;o=++s)if(r=t[o],!this._equal(r,e[o]))return!1;return!0}return t===e},e}()}); diff --git a/themes/HumHub/css/theme.css b/themes/HumHub/css/theme.css index 4c009bd3aa..85bdd6be43 100644 --- a/themes/HumHub/css/theme.css +++ b/themes/HumHub/css/theme.css @@ -1 +1 @@ -.colorDefault{color:#ededed}.backgroundDefault{background:#ededed}.borderDefault{border-color:#ededed}.colorPrimary{color:#708fa0 !important}.backgroundPrimary{background:#708fa0 !important}.borderPrimary{border-color:#708fa0 !important}.colorInfo{color:#6fdbe8 !important}.backgroundInfo{background:#6fdbe8 !important}.borderInfo{border-color:#6fdbe8 !important}.colorSuccess{color:#97d271 !important}.backgroundSuccess{background:#97d271 !important}.borderSuccess{border-color:#97d271 !important}.colorWarning{color:#fdd198 !important}.backgroundWarning{background:#fdd198 !important}.borderWarning{border-color:#fdd198 !important}.colorDanger{color:#ff8989 !important}.backgroundDanger{background:#ff8989 !important}.borderDanger{border-color:#ff8989 !important}.colorFont1{color:#bac2c7 !important}.colorFont2{color:#7a7a7a !important}.colorFont3{color:#555 !important}.colorFont4{color:#bebebe !important}.colorFont5{color:#aeaeae !important}.heading{font-size:16px;font-weight:300;color:#555;background-color:white;border:none;padding:10px}.text-center{text-align:center !important}.text-break{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.img-rounded{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}body{padding-top:130px;background-color:#ededed;color:#777;font-family:'Open Sans',sans-serif}body a,body a:hover,body a:focus,body a:active,body a.active{color:#555;text-decoration:none}a:hover{text-decoration:none}hr{margin-top:10px;margin-bottom:10px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{position:inherit}h4{font-weight:300;font-size:150%}input[type=text],input[type=password],input[type=select]{-webkit-appearance:none;-moz-appearance:none;appearance:none}.powered,.powered a{color:#b8c7d3 !important}.langSwitcher{display:inline-block}[data-ui-show-more]{overflow:hidden}.topbar{position:fixed;display:block;height:50px;width:100%;padding-left:15px;padding-right:15px}.topbar ul.nav{float:left}.topbar ul.nav>li{float:left}.topbar ul.nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}.topbar .dropdown-footer{margin:10px}.topbar .dropdown-header{font-size:16px;padding:3px 10px;margin-bottom:10px;font-weight:300;color:#bebebe}.topbar .dropdown-header .dropdown-header-link{position:absolute;top:2px;right:10px}.topbar .dropdown-header .dropdown-header-link a{color:#6fdbe8 !important;font-size:12px;font-weight:normal}.topbar .dropdown-header:hover{color:#bebebe}#topbar-first{background-color:#708fa0;top:0;z-index:1030;color:white}#topbar-first .nav>li>a:hover,#topbar-first .nav>.open>a{background-color:#8fa7b4}#topbar-first .nav>.account{height:50px;margin-left:20px}#topbar-first .nav>.account img{margin-left:10px}#topbar-first .nav>.account .dropdown-toggle{padding:10px 5px 8px;line-height:1.1em;text-align:left}#topbar-first .nav>.account .dropdown-toggle span{font-size:12px}#topbar-first .topbar-brand{position:relative;z-index:2}#topbar-first .topbar-actions{position:relative;z-index:3}#topbar-first .notifications{position:absolute;left:0;right:0;text-align:center;z-index:1}#topbar-first .notifications .btn-group{position:relative;text-align:left}#topbar-first .notifications .btn-group>a{padding:5px 10px;margin:10px 2px;display:inline-block;border-radius:2px;text-decoration:none;text-align:left}#topbar-first .notifications .btn-group>.label{position:absolute;top:4px;right:-2px}#topbar-first .notifications .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;border-width:10px;content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff;z-index:1035}#topbar-first .notifications .arrow{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;z-index:1001;border-width:11px;left:50%;margin-left:-18px;border-top-width:0;border-bottom-color:rgba(0,0,0,0.15);top:-19px;z-index:1035}#topbar-first .notifications .dropdown-menu{width:350px;margin-left:-148px}#topbar-first .notifications .dropdown-menu ul.media-list{max-height:400px;overflow:auto}#topbar-first .notifications .dropdown-menu li{position:relative}#topbar-first .notifications .dropdown-menu li i.approval{position:absolute;left:2px;top:36px;font-size:14px}#topbar-first .notifications .dropdown-menu li i.accepted{color:#5cb85c}#topbar-first .notifications .dropdown-menu li i.declined{color:#d9534f}#topbar-first .notifications .dropdown-menu li .media{position:relative}#topbar-first .notifications .dropdown-menu li .media .img-space{position:absolute;top:14px;left:14px}#topbar-first .dropdown-footer{margin:10px 10px 5px}#topbar-first a{color:white}#topbar-first .caret{border-top-color:#bebebe}#topbar-first .btn-group>a{background-color:#7f9baa}#topbar-first .btn-enter{background-color:#7f9baa;margin:6px 0}#topbar-first .btn-enter:hover{background-color:#89a2b0}#topbar-first .media-list a{color:#555;padding:0}#topbar-first .media-list li{color:#555}#topbar-first .media-list li i.accepted{color:#6fdbe8 !important}#topbar-first .media-list li i.declined{color:#ff8989 !important}#topbar-first .media-list li.placeholder{border-bottom:none}#topbar-first .media-list .media .media-body .label{padding:.1em .5em}#topbar-first .account .user-title{text-align:right}#topbar-first .account .user-title span{color:#d7d7d7}#topbar-first .dropdown.account>a,#topbar-first .dropdown.account.open>a,#topbar-first .dropdown.account>a:hover,#topbar-first .dropdown.account.open>a:hover{background-color:#708fa0}#topbar-second{top:50px;background-color:#fff;z-index:1029;background-image:none;-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1);border-bottom:1px solid #d4d4d4}#topbar-second .dropdown-menu{padding-top:0;padding-bottom:0}#topbar-second .dropdown-menu .divider{margin:0}#topbar-second #space-menu-dropdown,#topbar-second #search-menu-dropdown{width:400px}#topbar-second #space-menu-dropdown .media-list,#topbar-second #search-menu-dropdown .media-list{max-height:400px;overflow:auto}@media screen and (max-width:768px){#topbar-second #space-menu-dropdown .media-list,#topbar-second #search-menu-dropdown .media-list{max-height:200px}}#topbar-second #space-menu-dropdown form,#topbar-second #search-menu-dropdown form{margin:10px}#topbar-second #space-menu-dropdown .search-reset,#topbar-second #search-menu-dropdown .search-reset{position:absolute;color:#BFBFBF;margin:7px;top:0;right:40px;z-index:10;display:none;cursor:pointer}#topbar-second .nav>li>a{padding:7px 13px 0;text-decoration:none;text-shadow:none;font-weight:600;font-size:10px;min-height:50px;text-transform:uppercase;text-align:center}#topbar-second .nav>li>a:hover,#topbar-second .nav>li>a:active,#topbar-second .nav>li>a:focus{border-bottom:3px solid #6fdbe8;background-color:#f7f7f7;color:#555;text-decoration:none}#topbar-second .nav>li>a i{font-size:14px}#topbar-second .nav>li>a .caret{border-top-color:#7a7a7a}#topbar-second .nav>li.active>a{min-height:47px}#topbar-second .nav>li>ul>li>a{border-left:3px solid #fff;background-color:#fff;color:#555}#topbar-second .nav>li>ul>li>a:hover,#topbar-second .nav>li>ul>li>a.active{border-left:3px solid #6fdbe8;background-color:#f7f7f7;color:#555}#topbar-second .nav>li>a#space-menu{padding-right:13px;border-right:1px solid #ededed}#topbar-second .nav>li>a#search-menu{padding-top:15px}#topbar-second .nav>li>a:hover,#topbar-second .nav .open>a,#topbar-second .nav>li.active{border-bottom:3px solid #6fdbe8;background-color:#f7f7f7;color:#555}#topbar-second .nav>li.active>a:hover{border-bottom:none}#topbar-second #space-menu-dropdown li>ul>li>a>.media .media-body p{color:#bebebe;font-size:11px;margin:0;font-weight:400}@media (max-width:767px){.topbar{padding-left:0;padding-right:0}}.login-container{background-color:#708fa0;background-image:linear-gradient(to right, #708fa0 0, #8fa7b4 50%, #8fa7b4 100%),linear-gradient(to right, #7f9baa 0, #bdcbd3 51%, #adbfc9 100%);background-size:100% 100%;position:relative;padding-top:40px}.login-container .text{color:#fff;font-size:12px;margin-bottom:15px}.login-container .text a{color:#fff;text-decoration:underline}.login-container .panel a{color:#6fdbe8}.login-container h1,.login-container h2{color:#fff !important}.login-container .panel{box-shadow:0 0 15px #627d92;-moz-box-shadow:0 0 15px #627d92;-webkit-box-shadow:0 0 15px #627d92}.login-container .panel .panel-heading,.login-container .panel .panel-body{padding:15px}.login-container select{color:#555}#account-login-form .form-group{margin-bottom:10px}.dropdown-menu li a{font-size:13px !important;font-weight:600 !important}.dropdown-menu li a i{margin-right:5px;font-size:14px;display:inline-block;width:14px}.dropdown-menu li a:hover,.dropdown-menu li a:visited,.dropdown-menu li a:hover,.dropdown-menu li a:focus{background:none;cursor:pointer}.dropdown-menu li:hover,.dropdown-menu li.selected{color:#555}.dropdown-menu li:first-child{margin-top:3px}.dropdown-menu li:last-child{margin-bottom:3px}.modal .dropdown-menu,.panel .dropdown-menu,.nav-tabs .dropdown-menu{border:1px solid #d7d7d7}.modal .dropdown-menu li.divider,.panel .dropdown-menu li.divider,.nav-tabs .dropdown-menu li.divider{background-color:#f7f7f7;border-bottom:none;margin:9px 1px !important}.modal .dropdown-menu li,.panel .dropdown-menu li,.nav-tabs .dropdown-menu li{border-left:3px solid white}.modal .dropdown-menu li a,.panel .dropdown-menu li a,.nav-tabs .dropdown-menu li a{color:#555;font-size:14px;font-weight:400;padding:4px 15px}.modal .dropdown-menu li a i,.panel .dropdown-menu li a i,.nav-tabs .dropdown-menu li a i{margin-right:5px}.modal .dropdown-menu li a:hover,.panel .dropdown-menu li a:hover,.nav-tabs .dropdown-menu li a:hover{background:none}.modal .dropdown-menu li:hover,.panel .dropdown-menu li:hover,.nav-tabs .dropdown-menu li:hover,.modal .dropdown-menu li.selected,.panel .dropdown-menu li.selected,.nav-tabs .dropdown-menu li.selected{border-left:3px solid #6fdbe8;background-color:#f7f7f7 !important}ul.contextMenu{border:1px solid #d7d7d7}ul.contextMenu li.divider{background-color:#f7f7f7;border-bottom:none;margin:9px 1px !important}ul.contextMenu li{border-left:3px solid white}ul.contextMenu li a{color:#555;font-size:14px;font-weight:400;padding:4px 15px}ul.contextMenu li a i{margin-right:5px}ul.contextMenu li a:hover{background:none}ul.contextMenu li:hover,ul.contextMenu li.selected{border-left:3px solid #6fdbe8;background-color:#f7f7f7 !important}.media-list li{padding:10px;border-bottom:1px solid #eee;position:relative;border-left:3px solid white;font-size:12px}.media-list li a{color:#555}.media-list .badge-space-type{background-color:#f7f7f7;border:1px solid #d7d7d7;color:#b2b2b2;padding:3px 3px 2px 3px}.media-list li.new{border-left:3px solid #f3fcfd;background-color:#f3fcfd}.media-list li:hover,.media-list li.selected{background-color:#f7f7f7;border-left:3px solid #6fdbe8}.media-list li.placeholder{font-size:14px !important;border-bottom:none}.media-list li.placeholder:hover{background:none !important;border-left:3px solid white}.media-left,.media>.pull-left{padding-right:0;margin-right:10px}.media:after{content:'';clear:both;display:block}.media .time{font-size:11px;color:#bebebe}.media .img-space{position:absolute;top:35px;left:35px}.media .media-body{font-size:13px}.media .media-body h4.media-heading{font-size:14px;font-weight:500;color:#555}.media .media-body h4.media-heading a{color:#555}.media .media-body h4.media-heading small,.media .media-body h4.media-heading small a{font-size:11px;color:#bebebe}.media .media-body h4.media-heading .content{margin-right:35px}.media .media-body .content a{word-break:break-all}.media .media-body h5{color:#aeaeae;font-weight:300;margin-top:5px;margin-bottom:5px;min-height:15px}.media .media-body .module-controls{font-size:85%}.media .media-body .module-controls a{color:#6fdbe8}.media .content a{color:#6fdbe8}.media .content .files a{color:#555}.content span{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.panel{border:none;background-color:#fff;box-shadow:0 0 3px #dadada;-webkit-box-shadow:0 0 3px #dadada;-moz-box-shadow:0 0 3px #dadada;border-radius:4px;position:relative}.panel h1{font-size:16px;font-weight:300;margin-top:0;color:#555}.panel .panel-heading{font-size:16px;font-weight:300;color:#555;background-color:white;border:none;padding:10px;border-radius:4px}.panel .panel-heading .heading-link{color:#6fdbe8 !important;font-size:.8em}.panel .panel-body{padding:10px;font-size:13px}.panel .panel-body p{color:#555}.panel .statistics .entry{margin-left:20px;font-size:12px}.panel .statistics .entry .count{color:#6fdbe8;font-weight:600;font-size:20px;line-height:.8em}.panel h3.media-heading small{font-size:75%}.panel h3.media-heading small a{color:#6fdbe8}.panel-danger{border:2px solid #ff8989}.panel-danger .panel-heading{color:#ff8989}.panel-success{border:2px solid #97d271}.panel-success .panel-heading{color:#97d271}.panel-warning{border:2px solid #fdd198}.panel-warning .panel-heading{color:#fdd198}.panel.profile{position:relative}.panel.profile .controls{position:absolute;top:10px;right:10px}.panel.members .panel-body a img,.panel.groups .panel-body a img,.panel.follower .panel-body a img,.panel.spaces .panel-body a img{margin-bottom:5px}.panel-profile .panel-profile-header{position:relative;border:3px solid #fff;border-top-right-radius:3px;border-top-left-radius:3px}.panel-profile .panel-profile-header .img-profile-header-background{border-radius:3px;min-height:110px}.panel-profile .panel-profile-header .img-profile-data{position:absolute;height:100px;width:100%;bottom:0;left:0;padding-left:180px;padding-top:30px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;color:#fff;pointer-events:none;background:-moz-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,0,0)), color-stop(1%, rgba(0,0,0,0)), color-stop(100%, rgba(0,0,0,0.38)));background:-webkit-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:-o-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:-ms-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:linear-gradient(to bottom, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#94000000', GradientType=0)}.panel-profile .panel-profile-header .img-profile-data h1{font-size:30px;font-weight:100;margin-bottom:7px;color:#fff;max-width:600px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-profile .panel-profile-header .img-profile-data h2{font-size:16px;font-weight:400;margin-top:0}.panel-profile .panel-profile-header .img-profile-data h1.space{font-size:30px;font-weight:700}.panel-profile .panel-profile-header .img-profile-data h2.space{font-size:13px;font-weight:300;max-width:600px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-profile .panel-profile-header .profile-user-photo-container{position:absolute;bottom:-50px;left:15px}.panel-profile .panel-profile-header .profile-user-photo-container .profile-user-photo{border:3px solid #fff;border-radius:5px}.panel-profile .panel-profile-controls{padding-left:160px}.panel.pulse,.panel.fadeIn{-webkit-animation-duration:200ms;-moz-animation-duration:200ms;animation-duration:200ms}@media (max-width:767px){.panel-profile-controls{padding-left:0 !important;padding-top:50px}.panel-profile .panel-profile-header .img-profile-data h1{font-size:20px !important}}.panel-body>.tab-menu{margin-left:-10px;margin-right:-10px}.installer .logo{text-align:center}.installer h2{font-weight:100}.installer .panel{margin-top:50px}.installer .panel h3{margin-top:0}.installer .powered,.installer .powered a{color:#bac2c7 !important;margin-top:10px;font-size:12px}.installer .fa{width:18px}.installer .check-ok{color:#97d271}.installer .check-warning{color:#fdd198}.installer .check-error{color:#ff8989}.installer .prerequisites-list ul{list-style:none;padding-left:15px}.installer .prerequisites-list ul li{padding-bottom:5px}.pagination-container{text-align:center}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{background-color:#708fa0;border-color:#708fa0}.pagination>li>a,.pagination>li>span,.pagination>li>a:hover,.pagination>li>a:active,.pagination>li>a:focus{color:#555;cursor:pointer}.well-small{padding:10px;border-radius:3px}.well{border:none;box-shadow:none;background-color:#ededed;margin-bottom:1px}.well hr{margin:15px 0 10px;border-top:1px solid #d9d9d9}.well table>thead{font-size:11px}.tab-sub-menu{padding-left:10px}.tab-sub-menu li>a:hover,.tab-sub-menu li>a:focus{background-color:#f7f7f7;border-bottom-color:#ddd}.tab-sub-menu li.active>a{background-color:#fff;border-bottom-color:transparent}.tab-menu{padding-top:10px;background-color:#fff}.tab-menu .nav-tabs{padding-left:10px}.tab-menu .nav-tabs li>a{padding-top:12px;border-color:#ddd;border-bottom:1px solid #ddd;background-color:#f7f7f7;max-height:41px;outline:none}.tab-menu .nav-tabs li>a:hover,.tab-menu .nav-tabs li>a:focus{padding-top:10px;border-top:3px solid #ddd}.tab-menu .nav-tabs li>a:hover{background-color:#f7f7f7}.tab-menu .nav-tabs li.active>a,.tab-menu .nav-tabs li.active>a:hover{padding-top:10px;border-top:3px solid #6fdbe8}.tab-menu .nav-tabs li.active>a{background-color:#fff;border-bottom-color:transparent}ul.tab-menu{padding-top:10px;background-color:#fff;padding-left:10px}ul.tab-menu-settings li>a{padding-top:12px;border-color:#ddd;border-bottom:1px solid #ddd;background-color:#f7f7f7;max-height:41px;outline:none}ul.tab-menu-settings li>a:hover,ul.tab-menu-settings li>a:focus{padding-top:10px;border-top:3px solid #ddd !important}ul.tab-menu-settings li>a:hover{background-color:#f7f7f7}ul.tab-menu-settings li.active>a,ul.tab-menu-settings li.active>a:hover,ul.tab-menu-settings li.active>a:focus{padding-top:10px;border-top:3px solid #6fdbe8 !important}ul.tab-menu-settings li.active>a{background-color:#fff;border-bottom-color:transparent !important}.nav-pills .dropdown-menu,.nav-tabs .dropdown-menu,.account .dropdown-menu{background-color:#708fa0;border:none}.nav-pills .dropdown-menu li.divider,.nav-tabs .dropdown-menu li.divider,.account .dropdown-menu li.divider{background-color:#628394;border-bottom:none;margin:9px 1px !important}.nav-pills .dropdown-menu li,.nav-tabs .dropdown-menu li,.account .dropdown-menu li{border-left:3px solid #708fa0}.nav-pills .dropdown-menu li a,.nav-tabs .dropdown-menu li a,.account .dropdown-menu li a{color:white;font-weight:400;font-size:13px;padding:4px 15px}.nav-pills .dropdown-menu li a i,.nav-tabs .dropdown-menu li a i,.account .dropdown-menu li a i{margin-right:5px;font-size:14px;display:inline-block;width:14px}.nav-pills .dropdown-menu li a:hover,.nav-tabs .dropdown-menu li a:hover,.account .dropdown-menu li a:hover,.nav-pills .dropdown-menu li a:visited,.nav-tabs .dropdown-menu li a:visited,.account .dropdown-menu li a:visited,.nav-pills .dropdown-menu li a:hover,.nav-tabs .dropdown-menu li a:hover,.account .dropdown-menu li a:hover,.nav-pills .dropdown-menu li a:focus,.nav-tabs .dropdown-menu li a:focus,.account .dropdown-menu li a:focus{background:none}.nav-pills .dropdown-menu li:hover,.nav-tabs .dropdown-menu li:hover,.account .dropdown-menu li:hover,.nav-pills .dropdown-menu li.selected,.nav-tabs .dropdown-menu li.selected,.account .dropdown-menu li.selected{border-left:3px solid #6fdbe8;color:#fff !important;background-color:#628394 !important}.nav-pills.preferences .dropdown .dropdown-toggle{color:#bebebe}.nav-pills.preferences .dropdown.open .dropdown-toggle,.nav-pills.preferences .dropdown.open .dropdown-toggle:hover{background-color:#708fa0}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{background-color:#708fa0}.nav-tabs{margin-bottom:10px}.list-group a [class^="fa-"],.list-group a [class*=" fa-"]{display:inline-block;width:18px}.nav-pills.preferences{position:absolute;right:10px;top:10px}.nav-pills.preferences .dropdown .dropdown-toggle{padding:2px 10px}.nav-pills.preferences .dropdown.open .dropdown-toggle,.nav-pills.preferences .dropdown.open .dropdown-toggle:hover{color:white}.nav-tabs li{font-weight:600;font-size:12px}.tab-content .tab-pane a{color:#6fdbe8}.tab-content .tab-pane .form-group{margin-bottom:5px}.nav-tabs.tabs-center li{float:none;display:inline-block}.nav-tabs.tabs-small li>a{padding:5px 7px}.nav .caret,.nav .caret:hover,.nav .caret:active{border-top-color:#555;border-bottom-color:#555;height:6.928px}.nav li.dropdown>a:hover .caret,.nav li.dropdown>a:active .caret{border-top-color:#555;border-bottom-color:#555}.nav .open>a .caret,.nav .open>a:hover .caret,.nav .open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:#ededed;color:#555}.nav .open>a .caret,.nav .open>a:hover .caret,.nav .open>a:focus .caret{color:#555}@media (max-width:991px){.controls-header{text-align:left !important}}.btn{float:none;border:none;-webkit-box-shadow:none;box-shadow:none;-moz-box-shadow:none;background-image:none;text-shadow:none;border-radius:3px;outline:none !important;margin-bottom:0;font-size:14px;font-weight:600;padding:8px 16px}.input.btn{outline:none}.btn-lg{padding:16px 28px}.btn-sm{padding:4px 8px;font-size:12px}.btn-sm i{font-size:14px}.btn-xs{padding:1px 5px;font-size:12px}.btn-default{background:#ededed;color:#7a7a7a !important}.btn-default:hover,.btn-default:focus{background:#e8e8e8;text-decoration:none;color:#7a7a7a}.btn-default:active,.btn-default.active{outline:0;background:#e0e0e0}.btn-default[disabled],.btn-default.disabled{background:#f2f2f2}.btn-default[disabled]:hover,.btn-default.disabled:hover,.btn-default[disabled]:focus,.btn-default.disabled:focus{background:#f2f2f2}.btn-default[disabled]:active,.btn-default.disabled:active,.btn-default[disabled].active,.btn-default.disabled.active{background:#f2f2f2}.btn-primary{background:#708fa0;color:#fff !important}.btn-primary:hover,.btn-primary:focus{background:#628394;text-decoration:none}.btn-primary:active,.btn-primary.active{outline:0;background:#628394 !important}.btn-primary[disabled],.btn-primary.disabled{background:#7f9baa}.btn-primary[disabled]:hover,.btn-primary.disabled:hover,.btn-primary[disabled]:focus,.btn-primary.disabled:focus{background:#7f9baa}.btn-primary[disabled]:active,.btn-primary.disabled:active,.btn-primary[disabled].active,.btn-primary.disabled.active{background:#7f9baa !important}.btn-info{background:#6fdbe8;color:#fff !important}.btn-info:hover,.btn-info:focus{background:#59d6e4 !important;text-decoration:none}.btn-info:active,.btn-info.active{outline:0;background:#59d6e4}.btn-info[disabled],.btn-info.disabled{background:#85e0ec}.btn-info[disabled]:hover,.btn-info.disabled:hover,.btn-info[disabled]:focus,.btn-info.disabled:focus{background:#85e0ec}.btn-info[disabled]:active,.btn-info.disabled:active,.btn-info[disabled].active,.btn-info.disabled.active{background:#85e0ec !important}.btn-danger{background:#ff8989;color:#fff !important}.btn-danger:hover,.btn-danger:focus{background:#ff6f6f;text-decoration:none}.btn-danger:active,.btn-danger.active{outline:0;background:#ff6f6f !important}.btn-danger[disabled],.btn-danger.disabled{background:#ffa3a3}.btn-danger[disabled]:hover,.btn-danger.disabled:hover,.btn-danger[disabled]:focus,.btn-danger.disabled:focus{background:#ffa3a3}.btn-danger[disabled]:active,.btn-danger.disabled:active,.btn-danger[disabled].active,.btn-danger.disabled.active{background:#ffa3a3 !important}.btn-success{background:#97d271;color:#fff !important}.btn-success:hover,.btn-success:focus{background:#89cc5e;text-decoration:none}.btn-success:active,.btn-success.active{outline:0;background:#89cc5e !important}.btn-success[disabled],.btn-success.disabled{background:#a5d884}.btn-success[disabled]:hover,.btn-success.disabled:hover,.btn-success[disabled]:focus,.btn-success.disabled:focus{background:#a5d884}.btn-success[disabled]:active,.btn-success.disabled:active,.btn-success[disabled].active,.btn-success.disabled.active{background:#a5d884 !important}.btn-warning{background:#fdd198;color:#fff !important}.btn-warning:hover,.btn-warning:focus{background:#fdcd8e;text-decoration:none}.btn-warning:active,.btn-warning.active{outline:0;background:#fdcd8e !important}.btn-warning[disabled],.btn-warning.disabled{background:#fddcb1}.btn-warning[disabled]:hover,.btn-warning.disabled:hover,.btn-warning[disabled]:focus,.btn-warning.disabled:focus{background:#fddcb1}.btn-warning[disabled]:active,.btn-warning.disabled:active,.btn-warning[disabled].active,.btn-warning.disabled.active{background:#fddcb1 !important}.radio,.checkbox{margin-top:5px !important;margin-bottom:0}.radio label,.checkbox label{padding-left:10px}.form-control{border:2px solid #ededed;box-shadow:none;min-height:35px}.form-control:focus{border:2px solid #6fdbe8;outline:0;box-shadow:none}.form-control.form-search{border-radius:30px;background-image:url("../img/icon_search16x16.png");background-repeat:no-repeat;background-position:10px 8px;padding-left:34px}.form-group-search{position:relative}.form-group-search .form-button-search{position:absolute;top:4px;right:4px;border-radius:30px}textarea{resize:none;height:1.5em}select.form-control:not([multiple]){-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("../img/select_arrow.png") !important;background-repeat:no-repeat;background-position:right 13px;overflow:hidden}label{font-weight:normal}label.control-label{font-weight:bold}::-webkit-input-placeholder{color:#bebebe !important}::-moz-placeholder{color:#bebebe !important}:-ms-input-placeholder{color:#bebebe !important}input:-moz-placeholder{color:#bebebe !important}.placeholder{padding:10px}input.placeholder,textarea.placeholder{padding:0 0 0 10px;color:#999}.help-block-error{font-size:12px}.hint-block,.help-block:not(.help-block-error){color:#aeaeae !important;font-size:12px}.hint-block:hover,.help-block:not(.help-block-error):hover{color:#7a7a7a !important;font-size:12px}.input-group-addon{border:none}a.input-field-addon{font-size:12px;float:right;margin-top:-10px}a.input-field-addon-sm{font-size:11px;float:right;margin-top:-10px}.timeZoneInputContainer{padding-top:10px}.timeZoneInputContainer~.help-block{margin:0}.label{text-transform:uppercase}.label{text-transform:uppercase;display:inline-block;padding:3px 5px 4px;font-weight:600;font-size:10px !important;color:white !important;vertical-align:baseline;white-space:nowrap;text-shadow:none}.label-default{background:#ededed;color:#7a7a7a !important}a.label-default:hover{background:#e0e0e0 !important}.label-info{background-color:#6fdbe8}a.label-info:hover{background:#59d6e4 !important}.label-danger{background-color:#ff8989}a.label-danger:hover{background:#ff6f6f !important}.label-success{background-color:#97d271}a.label-success:hover{background:#89cc5e !important}.label-warning{background-color:#fdd198}a.label-warning:hover{background:#fdc67f !important}.regular-checkbox:checked+.regular-checkbox-box{border:2px solid #6fdbe8;background:#6fdbe8;color:white}.regular-checkbox-box.disabled{background:#d7d7d7 !important;border:2px solid #d7d7d7 !important;cursor:not-allowed}.regular-radio:checked+.regular-radio-button:after{background:#6fdbe8}.regular-radio:checked+.regular-radio-button{background-color:none;color:#99a1a7;border:2px solid #d7d7d7;margin-right:5px}.regular-radio.disabled{background:#d7d7d7 !important;border:2px solid #d7d7d7 !important;cursor:not-allowed}.errorMessage{color:#ff8989;padding:10px 0}.error{border-color:#ff8989 !important}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#ff8989 !important}.has-error .form-control,.has-error .form-control:focus{border-color:#ff8989;-webkit-box-shadow:none;box-shadow:none}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#97d271}.has-success .form-control,.has-success .form-control:focus{border-color:#97d271;-webkit-box-shadow:none;box-shadow:none}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#fdd198}.has-warning .form-control,.has-warning .form-control:focus{border-color:#fdd198;-webkit-box-shadow:none;box-shadow:none}.bootstrap-timepicker-widget .form-control{padding:0}#notification_overview_filter label{display:block}#notification_overview_list .img-space{position:absolute;top:25px;left:25px}@media (max-width:767px){.notifications{position:inherit !important;float:left !important}.notifications .dropdown-menu{width:300px !important;margin-left:0 !important}.notifications .dropdown-menu .arrow{margin-left:-142px !important}}.badge-space{margin-top:6px}.badge-space-chooser{padding:3px 5px;margin-left:1px}.badge{padding:3px 5px;border-radius:2px;font-weight:normal;font-family:Arial,sans-serif;font-size:10px !important;text-transform:uppercase;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:none;background-color:#d7d7d7;line-height:1}.popover{border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);-moz-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175)}.popover .popover-title{background:none;border-bottom:none;color:#555;font-weight:300;font-size:16px;padding:15px}.popover .popover-content{font-size:13px;padding:5px 15px;color:#555}.popover .popover-content a{color:#6fdbe8}.popover .popover-content img{max-width:100%}.popover .popover-navigation{padding:15px}.list-group-item{padding:6px 15px;border:none;border-width:0 !important;border-left:3px solid #fff !important;font-size:12px;font-weight:600}.list-group-item i{font-size:14px}a.list-group-item:hover,a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#555;background-color:#f7f7f7;border-left:3px solid #6fdbe8 !important}@media (max-width:991px){.list-group{margin-left:4px}.list-group-item{display:inline-block !important;border-radius:3px !important;margin:4px 0;margin-bottom:4px !important}.list-group-item{border:none !important}a.list-group-item:hover,a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border:none !important;background:#708fa0 !important;color:#fff !important}}@media screen and (max-width:768px){.modal-dialog{width:auto !important;padding-top:30px;padding-bottom:30px}}.modal-top{z-index:999999 !important}.modal{overflow-y:visible}.modal-dialog-extra-small{width:400px}.modal-dialog-small{width:500px}.modal-dialog-normal{width:600px}.modal-dialog-medium{width:768px}.modal-dialog-large{width:900px}@media screen and (max-width:920px){.modal-dialog-large{width:auto !important;padding-top:30px;padding-bottom:30px}}.modal{border:none}.modal h1,.modal h2,.modal h3,.modal h4,.modal h5{margin-top:20px;color:#555;font-weight:300}.modal h4.media-heading{margin-top:0}.modal-title{font-size:20px;font-weight:200;color:#555}.modal-dialog,.modal-content{min-width:150px}.modal-content{-webkit-border-radius:3px;-moz-border-radius:3px;box-shadow:0 2px 26px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.1);-webkit-box-shadow:0 2px 26px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.1);-moz-box-shadow:0 2px 26px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.1);border:none}.modal-content .modal-header{padding:20px 20px 0;border-bottom:none;text-align:center}.modal-content .modal-header .close{margin-top:2px;margin-right:5px}.modal-content .modal-body{padding:20px;font-size:13px}.modal-content .modal-footer{margin-top:0;text-align:left;padding:10px 20px 30px;border-top:none;text-align:center}.modal-content .modal-footer hr{margin-top:0}.modal-backdrop{background-color:rgba(0,0,0,0.5)}.modal-dialog.fadeIn,.modal-dialog.pulse{-webkit-animation-duration:200ms;-moz-animation-duration:200ms;animation-duration:200ms}.module-installed{opacity:.5}.module-installed .label-success{background-color:#d7d7d7}.tooltip-inner{background-color:#708fa0;max-width:400px;text-align:left;font-weight:300;padding:2px 8px 4px;font-weight:bold;white-space:pre-wrap}.tooltip.top .tooltip-arrow{border-top-color:#708fa0}.tooltip.top-left .tooltip-arrow{border-top-color:#708fa0}.tooltip.top-right .tooltip-arrow{border-top-color:#708fa0}.tooltip.right .tooltip-arrow{border-right-color:#708fa0}.tooltip.left .tooltip-arrow{border-left-color:#708fa0}.tooltip.bottom .tooltip-arrow{border-bottom-color:#708fa0}.tooltip.bottom-left .tooltip-arrow{border-bottom-color:#708fa0}.tooltip.bottom-right .tooltip-arrow{border-bottom-color:#708fa0}.tooltip.in{opacity:1;filter:alpha(opacity=100)}.progress{height:10px;margin-bottom:15px;box-shadow:none;background:#ededed;border-radius:10px}.progress-bar-info{background-color:#6fdbe8;-webkit-box-shadow:none;box-shadow:none}#nprogress .bar{height:2px;background:#6fdbe8}table{margin-bottom:0 !important}table th{font-size:11px;color:#bebebe;font-weight:normal}table thead tr th{border:none !important}table .time{font-size:12px}table td a:hover{color:#6fdbe8}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:10px 10px 10px 0}.table>thead>tr>th select,.table>tbody>tr>th select,.table>tfoot>tr>th select,.table>thead>tr>td select,.table>tbody>tr>td select,.table>tfoot>tr>td select{font-size:12px;padding:4px 8px;height:30px;margin:0}.table-middle>thead>tr>th,.table-middle>tbody>tr>th,.table-middle>tfoot>tr>th,.table-middle>thead>tr>td,.table-middle>tbody>tr>td,.table-middle>tfoot>tr>td{vertical-align:middle !important}.comment-container{margin-top:10px}.comment-container .wall-entry-controls{margin-left:50px}.comment .media{position:relative !important;margin-top:0}.comment .media .nav-pills.preferences{display:none;right:-3px;top:-3px}.comment .media-body{overflow:visible}.comment .jp-progress{background-color:#dbdcdd !important}.comment .jp-play-bar{background:#cacaca}.comment .content a{color:#21bdd0}.comment.guest-mode .media:last-child .wall-entry-controls{margin-bottom:0;margin-left:50px}.comment.guest-mode .media:last-child hr{display:none}.comment-container .content_edit{margin-left:50px}.comment_edit_content{margin-left:50px}.comment-message{overflow:hidden}.grid-view img{width:24px;height:24px}.grid-view .filters input,.grid-view .filters select{border:2px solid #ededed;box-shadow:none;min-height:35px;border-radius:4px;font-size:12px;padding:4px}.grid-view .filters input:focus,.grid-view .filters select:focus{border:2px solid #6fdbe8;outline:0;box-shadow:none}.grid-view{padding:15px 0 0}.grid-view img{border-radius:3px}.grid-view table th{font-size:13px !important;font-weight:bold !important}.grid-view table td{vertical-align:middle !important}.grid-view table tr{font-size:13px !important}.grid-view table thead tr th:first-of-type{padding-left:5px}.grid-view table tbody tr{height:50px}.grid-view table tbody tr td:first-of-type{padding-left:5px}.grid-view .summary{font-size:12px;color:#bac2c7}.permission-grid-editor>.table>tbody>tr:first-child>td{border:none}.permission-grid-editor{padding-top:0}.detail-view td,.detail-view th{padding:8px !important}.detail-view th{font-size:13px}.oembed_snippet{margin-top:10px;position:relative;padding-bottom:55%;padding-top:15px;height:0;overflow:hidden}.oembed_snippet iframe{position:absolute;top:0;left:0;width:100%;height:100%}.activities{max-height:400px;overflow:auto}.activities li .media{position:relative}.activities li .media .img-space{position:absolute;top:14px;left:14px}.activities li .media .media-body{max-width:295px}.contentForm_options{margin-top:10px;min-height:29px}.contentForm_options .btn_container{position:relative}.contentForm_options .btn_container .label-public{position:absolute;right:40px;top:11px}#contentFormError{color:#ff8989;padding-left:0;list-style:none}.placeholder-empty-stream{background-image:url("../img/placeholder-postform-arrow.png");background-repeat:no-repeat;padding:37px 0 0 70px;margin-left:90px}.wall-entry{position:relative}.wall-entry .content p,.wall-entry .content a{overflow:hidden;text-overflow:ellipsis;max-width:100%}.wall-entry .content img{max-width:100%}.wall-entry .media{overflow:visible}.wall-entry .well{margin-bottom:0}.wall-entry .well .comment .show-all-link{font-size:12px;cursor:pointer}.wall-entry .media-heading{font-size:14px;padding-top:1px;margin-bottom:3px}.wall-entry .media-heading .labels{padding-right:32px}.wall-entry .media-heading .viaLink{font-size:13px}.wall-entry .media-heading .viaLink i{color:#bebebe;padding-left:4px;padding-right:4px}.wall-entry .media-subheading{color:#bebebe;font-size:12px}.wall-entry .media-subheading .time{font-size:12px}.wall-entry-controls,.wall-entry-controls a{font-size:11px;font-weight: 700;color: #7a7a7a;margin-top:10px;margin-bottom:0}.wallFilterPanel li{font-size:11px;font-weight:600}.wallFilterPanel li a{color:#555}.wallFilterPanel .dropdown-menu li{margin-bottom:0}.wallFilterPanel .dropdown-menu li a{font-size:12px}.wallFilterPanel .dropdown-menu li a:hover{color:#fff !important}.stream-entry-loader{float:right;margin-top:5px}.load-suppressed{margin-top:-10px;margin-bottom:15px;text-align:center}.load-suppressed a{display:inline-block;background-color:white;padding:5px;border-radius:4px;border:1px solid #ddd;font-size:11px}.space-owner{text-align:center;margin:14px 0;font-size:13px;color:#999}.space-member-sign{color:#97d271;position:absolute;top:42px;left:42px;font-size:16px;background:#fff;width:24px;height:24px;padding:2px 3px 1px 4px;border-radius:50px;border:2px solid #97d271}#space-menu-dropdown i.type{font-size:16px;color:#BFBFBF}#space-menu-spaces [data-space-chooser-item]{cursor:pointer}#space-menu-dropdown .input-group-addon{border-radius:0 4px 4px 0}#space-menu-dropdown .input-group-addon.focus{border-radius:0 4px 4px 0;border:2px solid #6fdbe8;border-left:0}#space-menu-search{border-right:0}#space-directory-link i{margin-right:0}.space-acronym{color:#fff;text-align:center;display:inline-block}.current-space-image{margin-right:3px;margin-top:3px}@media (max-width:767px){#space-menu>.title{display:none}#space-menu-dropdown{width:300px !important}}.files,#postFormFiles_list{padding-left:0}.contentForm-upload-list{padding-left:0}.contentForm-upload-list li:first-child{margin-top:10px}.file_upload_remove_link,.file_upload_remove_link:hover{color:#ff8989;cursor:pointer}.file-preview-item{text-overflow:ellipsis;overflow:hidden}.post-files{margin-top:10px}.post-files img{vertical-align:top;margin-bottom:3px;margin-right:5px;max-height:130px;-webkit-animation-duration:2s;animation-duration:2s}#wallStream.mobile .post-files{margin-top:10px;display:flex;overflow-x:auto}#wallStream.mobile .post-files img{max-width:190px}.comment_create,.content_edit{position:relative}.comment_create .comment-buttons,.content_edit .comment-buttons{position:absolute;top:2px;right:5px}.comment_create .btn-comment-submit,.content_edit .btn-comment-submit{margin-top:3px}.comment_create .fileinput-button,.content_edit .fileinput-button{float:left;padding:6px 10px;background:transparent !important}.comment_create .fileinput-button .fa,.content_edit .fileinput-button .fa{color:#d7d7d7}.comment_create .fileinput-button:hover .fa,.content_edit .fileinput-button:hover .fa{background:transparent !important;color:#b2b2b2}.comment_create .fileinput-button:active,.content_edit .fileinput-button:active{box-shadow:none !important}.file-preview-content{cursor:pointer}.image-upload-container{position:relative}.image-upload-container .image-upload-buttons{display:none;position:absolute;right:5px;bottom:5px}.image-upload-container input[type="file"]{position:absolute;opacity:0}.image-upload-container .image-upload-loader{display:none;position:absolute;top:0;left:0;width:100%;height:100%;padding:20px;background:#f8f8f8}.mime{background-repeat:no-repeat;background-position:0 0;padding:1px 0 4px 26px}.mime-word{background-image:url("../img/mime/word.png")}.mime-excel{background-image:url("../img/mime/excel.png")}.mime-powerpoint{background-image:url("../img/mime/powerpoint.png")}.mime-pdf{background-image:url("../img/mime/pdf.png")}.mime-zip{background-image:url("../img/mime/zip.png")}.mime-image{background-image:url("../img/mime/image.png")}.mime-file{background-image:url("../img/mime/file.png")}.mime-photoshop{background-image:url("../img/mime/photoshop.png")}.mime-illustrator{background-image:url("../img/mime/illustrator.png")}.mime-video{background-image:url("../img/mime/video.png")}.mime-audio{background-image:url("../img/mime/audio.png")}ul.tour-list{list-style:none;margin-bottom:0;padding-left:10px}ul.tour-list li{padding-top:5px}ul.tour-list li a{color:#6fdbe8}ul.tour-list li a .fa{width:16px}ul.tour-list li.completed a{text-decoration:line-through;color:#bebebe}.atwho-view .cur{border-left:3px solid #6fdbe8;background-color:#f7f7f7 !important}.atwho-user,.atwho-space,.atwho-input a{color:#6fdbe8}.atwho-input a:hover{color:#6fdbe8}.atwho-view strong{background-color:#f9f0d2}.atwho-view .cur strong{background-color:#f9f0d2}.sk-spinner-three-bounce.sk-spinner{margin:0 auto;width:70px;text-align:center}.loader{padding:30px 0}.loader .sk-spinner-three-bounce div,.loader .sk-spinner-three-bounce span{width:12px;height:12px;background-color:#6fdbe8;border-radius:100%;display:inline-block;-webkit-animation:sk-threeBounceDelay 1.4s infinite ease-in-out;animation:sk-threeBounceDelay 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.loader .sk-spinner-three-bounce .sk-bounce1{-webkit-animation-delay:-0.32s;animation-delay:-0.32s}.loader .sk-spinner-three-bounce .sk-bounce2{-webkit-animation-delay:-0.16s;animation-delay:-0.16s}@-webkit-keyframes sk-threeBounceDelay{0%,80%,100%{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes sk-threeBounceDelay{0%,80%,100%{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}.loader-modal{padding:8px 0}.loader-postform{padding:9px 0}.loader-postform .sk-spinner-three-bounce.sk-spinner{text-align:left;margin:0}.markdown-render h1,.markdown-render h2,.markdown-render h3,.markdown-render h4,.markdown-render h5,.markdown-render h6{font-weight:bold !important}.markdown-render h1{font-size:28px !important}.markdown-render h2{font-size:24px !important}.markdown-render h3{font-size:18px !important}.markdown-render h4{font-size:16px !important}.markdown-render h5{font-size:14px !important}.markdown-render h6{color:#999;font-size:14px !important}.markdown-render pre{padding:0;border:none;border-radius:3px}.markdown-render pre code{padding:10px;border-radius:3px;font-size:12px !important}.markdown-render a,.markdown-render a:visited{background-color:inherit;text-decoration:none;color:#6fdbe8 !important}.markdown-render img{max-width:100%}.markdown-render table{width:100%}.markdown-render table th{font-size:13px;font-weight:700;color:#555}.markdown-render table thead tr{border-bottom:1px solid #d7d7d7}.markdown-render table tbody tr td,.markdown-render table thead tr th{border:1px solid #d7d7d7 !important;padding:4px}.md-editor.active{border:2px solid #6fdbe8 !important}.md-editor textarea{padding:10px !important}[data-ui-markdown] h1,[data-ui-markdown] h2,[data-ui-markdown] h3,[data-ui-markdown] h4,[data-ui-markdown] h5,[data-ui-markdown] h6{text-align:start;margin:0 0 .5em}[data-ui-markdown] h1{font-size:1.7em !important;font-weight:600}[data-ui-markdown] h2{font-size:1.5em !important;font-weight:500}[data-ui-markdown] h3{font-size:1.2em !important}[data-ui-markdown] h4{font-size:1.1em !important}[data-ui-markdown] h5{font-size:1em !important}[data-ui-markdown] h6{font-size:.85em !important}[data-ui-markdown] p,[data-ui-markdown] pre,[data-ui-markdown] blockquote{margin:0 0 1.1em}[data-ui-markdown] p:last-child{margin:0}[data-ui-markdown] blockquote{border-left-width:10px;background-color:rgba(128,128,128,0.05);border-top-right-radius:5px;border-bottom-right-radius:5px;padding:15px 20px;font-size:1em;border-left:5px solid #888888}[data-ui-markdown] table{margin-bottom:20px;max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}[data-ui-markdown] table caption+thead tr:first-child th,[data-ui-markdown] table caption+thead tr:first-child td,[data-ui-markdown] table colgroup+thead tr:first-child th,[data-ui-markdown] table colgroup+thead tr:first-child td,[data-ui-markdown] table thead:first-child tr:first-child th,[data-ui-markdown] table thead:first-child tr:first-child td{border-top:0}[data-ui-markdown] table thead th{vertical-align:bottom}[data-ui-markdown] table th{font-weight:bold;text-align:left}[data-ui-markdown] table th,[data-ui-markdown] table td{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #ddd}[data-ui-markdown] dt,[data-ui-markdown] dd{margin-top:5px;margin-bottom:5px;line-height:1.45}[data-ui-markdown] dt{font-weight:bold}[data-ui-markdown] dd{margin-left:40px}[data-ui-markdown] pre{text-align:start;border:0;padding:10px 20px;border-radius:5px}[data-ui-markdown] pre code{white-space:pre !important}[data-ui-markdown] blockquote ul:last-child,[data-ui-markdown] blockquote ol:last-child{margin-bottom:0}[data-ui-markdown] ul,[data-ui-markdown] ol{margin-top:0;margin-bottom:10.5px}[data-ui-markdown] ul li p,[data-ui-markdown] ol li p{overflow:visible !important}[data-ui-markdown] .footnote{vertical-align:top;position:relative;top:-0.5em;font-size:.8em}blockquote{border-left:2px dotted #888;padding-left:5px;background:#d0f0ff}.wmd-panel{min-width:500px}.wmd-button-bar{width:100%;background-color:Silver}.wmd-input{height:300px;width:100%;background-color:Gainsboro;border:1px solid DarkGray}.wmd-button-row{position:relative;margin-left:5px;margin-right:5px;margin-bottom:5px;margin-top:10px;padding:0;height:20px}.wmd-spacer{width:1px;height:20px;margin-left:14px;position:absolute;background-color:Silver;display:inline-block;list-style:none}.wmd-button{width:20px;height:20px;padding-left:2px;padding-right:3px;position:absolute;display:inline-block;list-style:none;cursor:pointer}.wmd-button>span{background-image:url(../img/wmd-buttons.png);background-repeat:no-repeat;background-position:0 0;width:20px;height:20px;display:inline-block}.wmd-spacer1{left:50px}.wmd-spacer2{left:175px}.wmd-spacer3{left:300px}.wmd-prompt-background{background-color:Black}.wmd-prompt-dialog{border:1px solid #999999;background-color:#F5F5F5}.wmd-prompt-dialog>div{font-size:.8em;font-family:arial,helvetica,sans-serif}.wmd-prompt-dialog>form>input[type="text"]{border:1px solid #999999;color:black}.wmd-prompt-dialog>form>input[type="button"]{border:1px solid #888888;font-family:trebuchet MS,helvetica,sans-serif;font-size:.8em;font-weight:bold}@media (max-width:991px){.layout-sidebar-container{display:none}}.ui-widget-header{border:none !important;background:#fff !important;color:#7a7a7a !important;font-weight:300 !important}.ui-widget-content{border:1px solid #dddcda !important;border-radius:0 !important;background:#fff;color:#555 !important;-webkit-box-shadow:0 6px 6px rgba(0,0,0,0.1);box-shadow:0 6px 6px rgba(0,0,0,0.1)}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{opacity:.2}.ui-datepicker .ui-datepicker-prev:hover,.ui-datepicker .ui-datepicker-next:hover{background:#fff !important;border:none;margin:1px}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:none !important;background:#f7f7f7 !important;color:#7a7a7a !important}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:none !important;border:1px solid #b2b2b2 !important}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #6fdbe8 !important;background:#ddf6fa !important}.status-bar-body{color:white;position:fixed;width:100%;background-color:rgba(0,0,0,0.7);text-align:center;padding:20px;z-index:9999999;bottom:0;display:block;line-height:20px}.status-bar-close{color:white;fonfont-weight:bold;font-size:21px;cursor:pointer}.status-bar-close:hover{color:white}.status-bar-close i{vertical-align:top !important;padding-top:3px}.status-bar-content i{margin-right:10px;font-size:21px;vertical-align:middle}.status-bar-content .showMore{color:#6fdbe8;float:right;margin-left:10px;font-size:.7em;cursor:pointer;vertical-align:middle;white-space:nowrap}.status-bar-content .status-bar-details{text-align:left;font-size:.7em;margin-top:20px;max-height:200px;overflow:auto}.status-bar-content span{vertical-align:middle}.status-bar-content i.error,.status-bar-content i.fatal{color:#ff8989}.status-bar-content i.warning{color:#fdd198}.status-bar-content i.info,.status-bar-content i.debug{color:#6fdbe8}.status-bar-content i.success{color:#85CA2B}.highlight{background-color:#fff8e0}.alert-default{color:#555;background-color:#f7f7f7;border-color:#ededed;font-size:13px}.alert-default .info{margin:10px 0}.alert-success{color:#84be5e;background-color:#f7fbf4;border-color:#97d271}.alert-warning{color:#e9b168;background-color:#fffbf7;border-color:#fdd198}.alert-danger{color:#ff8989;background-color:#fff6f6;border-color:#ff8989}.data-saved{padding-left:10px;color:#6fdbe8}img.bounceIn{-webkit-animation-duration:800ms;-moz-animation-duration:800ms;animation-duration:800ms}.tags .tag{margin-top:5px;border-radius:2px;padding:4px 8px;text-transform:uppercase;max-width:150px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}/*! Select2 humhub Theme v0.1.0-beta.4 | MIT License | github.com/select2/select2-humhub-theme */.select2-container--humhub{display:block}.select2-container--humhub .select2-selection{background-color:#fff;border:2px solid #ededed;border-radius:4px;color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;outline:0}.select2-container--humhub .select2-search--dropdown .select2-search__field{background-color:#fff;border:2px solid #ededed;border-radius:4px;color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px}.select2-container--humhub .select2-search__field{outline:0}.select2-container--humhub .select2-search__field::-webkit-input-placeholder{color:#999}.select2-container--humhub .select2-search__field:-moz-placeholder{color:#999}.select2-container--humhub .select2-search__field::-moz-placeholder{color:#999;opacity:1}.select2-container--humhub .select2-search__field:-ms-input-placeholder{color:#999}.select2-container--humhub .select2-results__option[role=group]{padding:0}.select2-container--humhub .select2-results__option[aria-disabled=true]{color:#777;cursor:not-allowed}.select2-container--humhub .select2-results__option[aria-selected=true]{background-color:#f5f5f5;color:#262626;border-left:3px solid transparent}.select2-container--humhub .select2-results__option[aria-selected=false]{border-left:3px solid transparent}.select2-container--humhub .select2-results__option--highlighted[aria-selected]{background-color:#f7f7f7;border-left:3px solid #6fdbe8;color:#555}.select2-container--humhub .select2-results__option .select2-results__option{padding:6px 12px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option{margin-left:-12px;padding-left:24px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-24px;padding-left:36px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-36px;padding-left:48px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-48px;padding-left:60px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-60px;padding-left:72px}.select2-container--humhub .select2-results__group{color:#777;display:block;padding:6px 12px;font-size:12px;line-height:1.42857143;white-space:nowrap}.select2-container--humhub.select2-container--focus .select2-selection,.select2-container--humhub.select2-container--open .select2-selection{border:2px solid #6fdbe8;outline:0;box-shadow:none}.select2-container--humhub.select2-container--open .select2-selection .select2-selection__arrow b{border-color:transparent transparent #999 transparent;border-width:0 4px 4px 4px}.select2-container--humhub .select2-selection__clear{color:#999;cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--humhub .select2-selection__clear:hover{color:#333}.select2-container--humhub.select2-container--disabled .select2-selection{border-color:#ccc;-webkit-box-shadow:none;box-shadow:none}.select2-container--humhub.select2-container--disabled .select2-selection,.select2-container--humhub.select2-container--disabled .select2-search__field{cursor:not-allowed}.select2-container--humhub.select2-container--disabled .select2-selection,.select2-container--humhub.select2-container--disabled .select2-selection--multiple .select2-selection__choice{background-color:#eee}.select2-container--humhub.select2-container--disabled .select2-selection__clear,.select2-container--humhub.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove{display:none}.select2-container--humhub .select2-dropdown{-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);border-color:#d7d7d7;overflow-x:hidden;margin-top:-1px}.select2-container--humhub .select2-dropdown--above{margin-top:1px}.select2-container--humhub .select2-results>.select2-results__options{max-height:400px;overflow-y:auto}.select2-container--humhub .select2-selection--single{height:34px;line-height:1.42857143;padding:6px 24px 6px 12px}.select2-container--humhub .select2-selection--single .select2-selection__arrow{position:absolute;bottom:0;right:12px;top:0;width:4px}.select2-container--humhub .select2-selection--single .select2-selection__arrow b{border-color:#999 transparent transparent transparent;border-style:solid;border-width:4px 4px 0 4px;height:0;left:0;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--humhub .select2-selection--single .select2-selection__rendered{color:#555;padding:0}.select2-container--humhub .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--humhub .select2-selection--multiple{min-height:34px;padding:2px}.select2-container--humhub .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;display:block;line-height:1.42857143;list-style:none;margin:0;overflow:hidden;padding:0;width:100%;text-overflow:ellipsis;white-space:nowrap}.select2-container--humhub .select2-selection--multiple .select2-selection__placeholder{color:#999;float:left;margin-top:5px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice{color:#555;border-radius:4px;cursor:default;padding:0 6px;background-color:#6fdbe8;color:#fff;border-radius:3px;font-size:12px !important;padding:2px 5px 2px 2px;float:left;margin:2px;height:28px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice img,.select2-container--humhub .select2-selection--multiple .select2-selection__choice div{margin-right:5px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice span.no-image{line-height:27px;padding-left:5px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice i{margin:0 2px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice .picker-close{cursor:pointer}.select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field{background:transparent;padding:0 5px;width:auto !important;height:32px;line-height:1.42857143;margin-top:0;min-width:5em}.select2-container--humhub .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:none;font-weight:bold;margin-right:3px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--humhub .select2-selection--multiple .select2-selection__clear{margin-top:6px}.select2-container--humhub.input-sm,.select2-container--humhub.input-lg{border-radius:0;font-size:12px;height:auto;line-height:1;padding:0}.select2-container--humhub.input-sm .select2-selection--single,.input-group-sm .select2-container--humhub .select2-selection--single,.form-group-sm .select2-container--humhub .select2-selection--single{border-radius:3px;font-size:12px;height:30px;line-height:1.5;padding:5px 22px 5px 10px}.select2-container--humhub.input-sm .select2-selection--single .select2-selection__arrow b,.input-group-sm .select2-container--humhub .select2-selection--single .select2-selection__arrow b,.form-group-sm .select2-container--humhub .select2-selection--single .select2-selection__arrow b{margin-left:-5px}.select2-container--humhub.input-sm .select2-selection--multiple,.input-group-sm .select2-container--humhub .select2-selection--multiple,.form-group-sm .select2-container--humhub .select2-selection--multiple{min-height:30px}.select2-container--humhub.input-sm .select2-selection--multiple .select2-selection__choice,.input-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__choice,.form-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__choice{font-size:12px;line-height:1.5;margin:4px 0 0 5px;padding:0 5px}.select2-container--humhub.input-sm .select2-selection--multiple .select2-search--inline .select2-search__field,.input-group-sm .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field,.form-group-sm .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field{padding:0 10px;font-size:12px;height:28px;line-height:1.5}.select2-container--humhub.input-sm .select2-selection--multiple .select2-selection__clear,.input-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__clear,.form-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__clear{margin-top:5px}.select2-container--humhub.input-lg .select2-selection--single,.input-group-lg .select2-container--humhub .select2-selection--single,.form-group-lg .select2-container--humhub .select2-selection--single{border-radius:6px;font-size:18px;height:46px;line-height:1.3333333;padding:10px 31px 10px 16px}.select2-container--humhub.input-lg .select2-selection--single .select2-selection__arrow,.input-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow,.form-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow{width:5px}.select2-container--humhub.input-lg .select2-selection--single .select2-selection__arrow b,.input-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow b,.form-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow b{border-width:5px 5px 0 5px;margin-left:-5px;margin-left:-10px;margin-top:-2.5px}.select2-container--humhub.input-lg .select2-selection--multiple,.input-group-lg .select2-container--humhub .select2-selection--multiple,.form-group-lg .select2-container--humhub .select2-selection--multiple{min-height:46px}.select2-container--humhub.input-lg .select2-selection--multiple .select2-selection__choice,.input-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__choice,.form-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__choice{font-size:18px;line-height:1.3333333;border-radius:4px;margin:9px 0 0 8px;padding:0 10px}.select2-container--humhub.input-lg .select2-selection--multiple .select2-search--inline .select2-search__field,.input-group-lg .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field,.form-group-lg .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field{padding:0 16px;font-size:18px;height:44px;line-height:1.3333333}.select2-container--humhub.input-lg .select2-selection--multiple .select2-selection__clear,.input-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__clear,.form-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__clear{margin-top:10px}.select2-container--humhub.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #999 transparent;border-width:0 5px 5px 5px}.input-group-lg .select2-container--humhub.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #999 transparent;border-width:0 5px 5px 5px}.select2-container--humhub[dir="rtl"] .select2-selection--single{padding-left:24px;padding-right:12px}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:0;padding-left:0;text-align:right}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:12px;right:auto}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__arrow b{margin-left:0}.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder{float:right}.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:0;margin-right:6px}.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.has-warning .select2-dropdown,.has-warning .select2-selection{border-color:#fdd198}.has-warning .select2-container--focus .select2-selection,.has-warning .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fffefc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fffefc;border-color:#fcbb66}.has-warning.select2-drop-active{border-color:#fcbb66}.has-warning.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#fcbb66}.has-error .select2-dropdown,.has-error .select2-selection{border-color:#ff8989}.has-error .select2-container--focus .select2-selection,.has-error .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ffefef;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ffefef;border-color:#ff5656}.has-error.select2-drop-active{border-color:#ff5656}.has-error.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#ff5656}.has-success .select2-dropdown,.has-success .select2-selection{border-color:#97d271}.has-success .select2-container--focus .select2-selection,.has-success .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d0ebbe;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d0ebbe;border-color:#7bc64a}.has-success.select2-drop-active{border-color:#7bc64a}.has-success.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#7bc64a}.input-group .select2-container--humhub{display:table;table-layout:fixed;position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group.select2-humhub-prepend .select2-container--humhub .select2-selection{border-bottom-left-radius:0;border-top-left-radius:0}.input-group.select2-humhub-append .select2-container--humhub .select2-selection{border-bottom-right-radius:0;border-top-right-radius:0}.select2-humhub-append .select2-container--humhub,.select2-humhub-prepend .select2-container--humhub,.select2-humhub-append .input-group-btn,.select2-humhub-prepend .input-group-btn,.select2-humhub-append .input-group-btn .btn,.select2-humhub-prepend .input-group-btn .btn{vertical-align:top}.form-control.select2-hidden-accessible{position:absolute !important;width:1px !important}.form-inline .select2-container--humhub{display:inline-block}ul.tag_input{list-style:none;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;padding:0 0 9px 4px}ul.tag_input li img{margin:0 5px 0 0}.tag_input_field{outline:none;border:none !important;padding:5px 4px 0 !important;width:170px;margin:2px 0 0 !important}.userInput,.spaceInput{background-color:#6fdbe8;font-weight:600;color:#fff;border-radius:3px;font-size:12px !important;padding:2px;float:left;margin:3px 4px 0 0}.userInput i,.spaceInput i{padding:0 6px;font-size:14px;cursor:pointer;line-height:8px} +.colorDefault{color:#ededed}.backgroundDefault{background:#ededed}.borderDefault{border-color:#ededed}.colorPrimary{color:#708fa0 !important}.backgroundPrimary{background:#708fa0 !important}.borderPrimary{border-color:#708fa0 !important}.colorInfo{color:#6fdbe8 !important}.backgroundInfo{background:#6fdbe8 !important}.borderInfo{border-color:#6fdbe8 !important}.colorSuccess{color:#97d271 !important}.backgroundSuccess{background:#97d271 !important}.borderSuccess{border-color:#97d271 !important}.colorWarning{color:#fdd198 !important}.backgroundWarning{background:#fdd198 !important}.borderWarning{border-color:#fdd198 !important}.colorDanger{color:#ff8989 !important}.backgroundDanger{background:#ff8989 !important}.borderDanger{border-color:#ff8989 !important}.colorFont1{color:#bac2c7 !important}.colorFont2{color:#7a7a7a !important}.colorFont3{color:#555 !important}.colorFont4{color:#bebebe !important}.colorFont5{color:#aeaeae !important}.heading{font-size:16px;font-weight:300;color:#555;background-color:white;border:none;padding:10px}.text-center{text-align:center !important}.text-break{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.img-rounded{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}body{padding-top:130px;background-color:#ededed;color:#777;font-family:'Open Sans',sans-serif}body a,body a:hover,body a:focus,body a:active,body a.active{color:#555;text-decoration:none}a:hover{text-decoration:none}hr{margin-top:10px;margin-bottom:10px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{position:inherit}h4{font-weight:300;font-size:150%}input[type=text],input[type=password],input[type=select]{-webkit-appearance:none;-moz-appearance:none;appearance:none}.powered,.powered a{color:#b8c7d3 !important}.langSwitcher{display:inline-block}[data-ui-show-more]{overflow:hidden}.topbar{position:fixed;display:block;height:50px;width:100%;padding-left:15px;padding-right:15px}.topbar ul.nav{float:left}.topbar ul.nav>li{float:left}.topbar ul.nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}.topbar .dropdown-footer{margin:10px}.topbar .dropdown-header{font-size:16px;padding:3px 10px;margin-bottom:10px;font-weight:300;color:#bebebe}.topbar .dropdown-header .dropdown-header-link{position:absolute;top:2px;right:10px}.topbar .dropdown-header .dropdown-header-link a{color:#6fdbe8 !important;font-size:12px;font-weight:normal}.topbar .dropdown-header:hover{color:#bebebe}#topbar-first{background-color:#708fa0;top:0;z-index:1030;color:white}#topbar-first .nav>li>a:hover,#topbar-first .nav>.open>a{background-color:#8fa7b4}#topbar-first .nav>.account{height:50px;margin-left:20px}#topbar-first .nav>.account img{margin-left:10px}#topbar-first .nav>.account .dropdown-toggle{padding:10px 5px 8px;line-height:1.1em;text-align:left}#topbar-first .nav>.account .dropdown-toggle span{font-size:12px}#topbar-first .topbar-brand{position:relative;z-index:2}#topbar-first .topbar-actions{position:relative;z-index:3}#topbar-first .notifications{position:absolute;left:0;right:0;text-align:center;z-index:1}#topbar-first .notifications .btn-group{position:relative;text-align:left}#topbar-first .notifications .btn-group>a{padding:5px 10px;margin:10px 2px;display:inline-block;border-radius:2px;text-decoration:none;text-align:left}#topbar-first .notifications .btn-group>.label{position:absolute;top:4px;right:-2px}#topbar-first .notifications .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;border-width:10px;content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff;z-index:1035}#topbar-first .notifications .arrow{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;z-index:1001;border-width:11px;left:50%;margin-left:-18px;border-top-width:0;border-bottom-color:rgba(0,0,0,0.15);top:-19px;z-index:1035}#topbar-first .notifications .dropdown-menu{width:350px;margin-left:-148px}#topbar-first .notifications .dropdown-menu ul.media-list{max-height:400px;overflow:auto}#topbar-first .notifications .dropdown-menu li{position:relative}#topbar-first .notifications .dropdown-menu li i.approval{position:absolute;left:2px;top:36px;font-size:14px}#topbar-first .notifications .dropdown-menu li i.accepted{color:#5cb85c}#topbar-first .notifications .dropdown-menu li i.declined{color:#d9534f}#topbar-first .notifications .dropdown-menu li .media{position:relative}#topbar-first .notifications .dropdown-menu li .media .img-space{position:absolute;top:14px;left:14px}#topbar-first .dropdown-footer{margin:10px 10px 5px}#topbar-first a{color:white}#topbar-first .caret{border-top-color:#bebebe}#topbar-first .btn-group>a{background-color:#7f9baa}#topbar-first .btn-enter{background-color:#7f9baa;margin:6px 0}#topbar-first .btn-enter:hover{background-color:#89a2b0}#topbar-first .media-list a{color:#555;padding:0}#topbar-first .media-list li{color:#555}#topbar-first .media-list li i.accepted{color:#6fdbe8 !important}#topbar-first .media-list li i.declined{color:#ff8989 !important}#topbar-first .media-list li.placeholder{border-bottom:none}#topbar-first .media-list .media .media-body .label{padding:.1em .5em}#topbar-first .account .user-title{text-align:right}#topbar-first .account .user-title span{color:#d7d7d7}#topbar-first .dropdown.account>a,#topbar-first .dropdown.account.open>a,#topbar-first .dropdown.account>a:hover,#topbar-first .dropdown.account.open>a:hover{background-color:#708fa0}#topbar-second{top:50px;background-color:#fff;z-index:1029;background-image:none;-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1);border-bottom:1px solid #d4d4d4}#topbar-second .dropdown-menu{padding-top:0;padding-bottom:0}#topbar-second .dropdown-menu .divider{margin:0}#topbar-second #space-menu-dropdown,#topbar-second #search-menu-dropdown{width:400px}#topbar-second #space-menu-dropdown .media-list,#topbar-second #search-menu-dropdown .media-list{max-height:400px;overflow:auto}@media screen and (max-width:768px){#topbar-second #space-menu-dropdown .media-list,#topbar-second #search-menu-dropdown .media-list{max-height:200px}}#topbar-second #space-menu-dropdown form,#topbar-second #search-menu-dropdown form{margin:10px}#topbar-second #space-menu-dropdown .search-reset,#topbar-second #search-menu-dropdown .search-reset{position:absolute;color:#BFBFBF;margin:7px;top:0;right:40px;z-index:10;display:none;cursor:pointer}#topbar-second .nav>li>a{padding:7px 13px 0;text-decoration:none;text-shadow:none;font-weight:600;font-size:10px;min-height:50px;text-transform:uppercase;text-align:center}#topbar-second .nav>li>a:hover,#topbar-second .nav>li>a:active,#topbar-second .nav>li>a:focus{border-bottom:3px solid #6fdbe8;background-color:#f7f7f7;color:#555;text-decoration:none}#topbar-second .nav>li>a i{font-size:14px}#topbar-second .nav>li>a .caret{border-top-color:#7a7a7a}#topbar-second .nav>li.active>a{min-height:47px}#topbar-second .nav>li>ul>li>a{border-left:3px solid #fff;background-color:#fff;color:#555}#topbar-second .nav>li>ul>li>a:hover,#topbar-second .nav>li>ul>li>a.active{border-left:3px solid #6fdbe8;background-color:#f7f7f7;color:#555}#topbar-second .nav>li>a#space-menu{padding-right:13px;border-right:1px solid #ededed}#topbar-second .nav>li>a#search-menu{padding-top:15px}#topbar-second .nav>li>a:hover,#topbar-second .nav .open>a,#topbar-second .nav>li.active{border-bottom:3px solid #6fdbe8;background-color:#f7f7f7;color:#555}#topbar-second .nav>li.active>a:hover{border-bottom:none}#topbar-second #space-menu-dropdown li>ul>li>a>.media .media-body p{color:#bebebe;font-size:11px;margin:0;font-weight:400}@media (max-width:767px){.topbar{padding-left:0;padding-right:0}}.login-container{background-color:#708fa0;background-image:linear-gradient(to right, #708fa0 0, #8fa7b4 50%, #8fa7b4 100%),linear-gradient(to right, #7f9baa 0, #bdcbd3 51%, #adbfc9 100%);background-size:100% 100%;position:relative;padding-top:40px}.login-container .text{color:#fff;font-size:12px;margin-bottom:15px}.login-container .text a{color:#fff;text-decoration:underline}.login-container .panel a{color:#6fdbe8}.login-container h1,.login-container h2{color:#fff !important}.login-container .panel{box-shadow:0 0 15px #627d92;-moz-box-shadow:0 0 15px #627d92;-webkit-box-shadow:0 0 15px #627d92}.login-container .panel .panel-heading,.login-container .panel .panel-body{padding:15px}.login-container select{color:#555}#account-login-form .form-group{margin-bottom:10px}.dropdown-menu li a{font-size:13px !important;font-weight:600 !important}.dropdown-menu li a i{margin-right:5px;font-size:14px;display:inline-block;width:14px}.dropdown-menu li a:hover,.dropdown-menu li a:visited,.dropdown-menu li a:hover,.dropdown-menu li a:focus{background:none;cursor:pointer}.dropdown-menu li:hover,.dropdown-menu li.selected{color:#555}.dropdown-menu li:first-child{margin-top:3px}.dropdown-menu li:last-child{margin-bottom:3px}.modal .dropdown-menu,.panel .dropdown-menu,.nav-tabs .dropdown-menu{border:1px solid #d7d7d7}.modal .dropdown-menu li.divider,.panel .dropdown-menu li.divider,.nav-tabs .dropdown-menu li.divider{background-color:#f7f7f7;border-bottom:none;margin:9px 1px !important}.modal .dropdown-menu li,.panel .dropdown-menu li,.nav-tabs .dropdown-menu li{border-left:3px solid white}.modal .dropdown-menu li a,.panel .dropdown-menu li a,.nav-tabs .dropdown-menu li a{color:#555;font-size:14px;font-weight:400;padding:4px 15px}.modal .dropdown-menu li a i,.panel .dropdown-menu li a i,.nav-tabs .dropdown-menu li a i{margin-right:5px}.modal .dropdown-menu li a:hover,.panel .dropdown-menu li a:hover,.nav-tabs .dropdown-menu li a:hover{background:none}.modal .dropdown-menu li:hover,.panel .dropdown-menu li:hover,.nav-tabs .dropdown-menu li:hover,.modal .dropdown-menu li.selected,.panel .dropdown-menu li.selected,.nav-tabs .dropdown-menu li.selected{border-left:3px solid #6fdbe8;background-color:#f7f7f7 !important}ul.contextMenu{border:1px solid #d7d7d7}ul.contextMenu li.divider{background-color:#f7f7f7;border-bottom:none;margin:9px 1px !important}ul.contextMenu li{border-left:3px solid white}ul.contextMenu li a{color:#555;font-size:14px;font-weight:400;padding:4px 15px}ul.contextMenu li a i{margin-right:5px}ul.contextMenu li a:hover{background:none}ul.contextMenu li:hover,ul.contextMenu li.selected{border-left:3px solid #6fdbe8;background-color:#f7f7f7 !important}.media-list li{padding:10px;border-bottom:1px solid #eee;position:relative;border-left:3px solid white;font-size:12px}.media-list li a{color:#555}.media-list .badge-space-type{background-color:#f7f7f7;border:1px solid #d7d7d7;color:#b2b2b2;padding:3px 3px 2px 3px}.media-list li.new{border-left:3px solid #f3fcfd;background-color:#f3fcfd}.media-list li:hover,.media-list li.selected{background-color:#f7f7f7;border-left:3px solid #6fdbe8}.media-list li.placeholder{font-size:14px !important;border-bottom:none}.media-list li.placeholder:hover{background:none !important;border-left:3px solid white}.media-left,.media>.pull-left{padding-right:0;margin-right:10px}.media:after{content:'';clear:both;display:block}.media .time{font-size:11px;color:#bebebe}.media .img-space{position:absolute;top:35px;left:35px}.media .media-body{font-size:13px}.media .media-body h4.media-heading{font-size:14px;font-weight:500;color:#555}.media .media-body h4.media-heading a{color:#555}.media .media-body h4.media-heading small,.media .media-body h4.media-heading small a{font-size:11px;color:#bebebe}.media .media-body h4.media-heading .content{margin-right:35px}.media .media-body .content a{word-break:break-all}.media .media-body h5{color:#aeaeae;font-weight:300;margin-top:5px;margin-bottom:5px;min-height:15px}.media .media-body .module-controls{font-size:85%}.media .media-body .module-controls a{color:#6fdbe8}.media .content a{color:#6fdbe8}.media .content .files a{color:#555}.content span{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-ms-hyphens:auto;-moz-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.panel{border:none;background-color:#fff;box-shadow:0 0 3px #dadada;-webkit-box-shadow:0 0 3px #dadada;-moz-box-shadow:0 0 3px #dadada;border-radius:4px;position:relative}.panel h1{font-size:16px;font-weight:300;margin-top:0;color:#555}.panel .panel-heading{font-size:16px;font-weight:300;color:#555;background-color:white;border:none;padding:10px;border-radius:4px}.panel .panel-heading .heading-link{color:#6fdbe8 !important;font-size:.8em}.panel .panel-body{padding:10px;font-size:13px}.panel .panel-body p{color:#555}.panel .statistics .entry{margin-left:20px;font-size:12px}.panel .statistics .entry .count{color:#6fdbe8;font-weight:600;font-size:20px;line-height:.8em}.panel h3.media-heading small{font-size:75%}.panel h3.media-heading small a{color:#6fdbe8}.panel-danger{border:2px solid #ff8989}.panel-danger .panel-heading{color:#ff8989}.panel-success{border:2px solid #97d271}.panel-success .panel-heading{color:#97d271}.panel-warning{border:2px solid #fdd198}.panel-warning .panel-heading{color:#fdd198}.panel.profile{position:relative}.panel.profile .controls{position:absolute;top:10px;right:10px}.panel.members .panel-body a img,.panel.groups .panel-body a img,.panel.follower .panel-body a img,.panel.spaces .panel-body a img{margin-bottom:5px}.panel-profile .panel-profile-header{position:relative;border:3px solid #fff;border-top-right-radius:3px;border-top-left-radius:3px}.panel-profile .panel-profile-header .img-profile-header-background{border-radius:3px;min-height:110px}.panel-profile .panel-profile-header .img-profile-data{position:absolute;height:100px;width:100%;bottom:0;left:0;padding-left:180px;padding-top:30px;border-bottom-right-radius:3px;border-bottom-left-radius:3px;color:#fff;pointer-events:none;background:-moz-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,0,0)), color-stop(1%, rgba(0,0,0,0)), color-stop(100%, rgba(0,0,0,0.38)));background:-webkit-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:-o-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:-ms-linear-gradient(top, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);background:linear-gradient(to bottom, rgba(0,0,0,0) 0, rgba(0,0,0,0) 1%, rgba(0,0,0,0.38) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#94000000', GradientType=0)}.panel-profile .panel-profile-header .img-profile-data h1{font-size:30px;font-weight:100;margin-bottom:7px;color:#fff;max-width:600px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-profile .panel-profile-header .img-profile-data h2{font-size:16px;font-weight:400;margin-top:0}.panel-profile .panel-profile-header .img-profile-data h1.space{font-size:30px;font-weight:700}.panel-profile .panel-profile-header .img-profile-data h2.space{font-size:13px;font-weight:300;max-width:600px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.panel-profile .panel-profile-header .profile-user-photo-container{position:absolute;bottom:-50px;left:15px}.panel-profile .panel-profile-header .profile-user-photo-container .profile-user-photo{border:3px solid #fff;border-radius:5px}.panel-profile .panel-profile-controls{padding-left:160px}.panel.pulse,.panel.fadeIn{-webkit-animation-duration:200ms;-moz-animation-duration:200ms;animation-duration:200ms}@media (max-width:767px){.panel-profile-controls{padding-left:0 !important;padding-top:50px}.panel-profile .panel-profile-header .img-profile-data h1{font-size:20px !important}}.panel-body>.tab-menu{margin-left:-10px;margin-right:-10px}.installer .logo{text-align:center}.installer h2{font-weight:100}.installer .panel{margin-top:50px}.installer .panel h3{margin-top:0}.installer .powered,.installer .powered a{color:#bac2c7 !important;margin-top:10px;font-size:12px}.installer .fa{width:18px}.installer .check-ok{color:#97d271}.installer .check-warning{color:#fdd198}.installer .check-error{color:#ff8989}.installer .prerequisites-list ul{list-style:none;padding-left:15px}.installer .prerequisites-list ul li{padding-bottom:5px}.pagination-container{text-align:center}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{background-color:#708fa0;border-color:#708fa0}.pagination>li>a,.pagination>li>span,.pagination>li>a:hover,.pagination>li>a:active,.pagination>li>a:focus{color:#555;cursor:pointer}.well-small{padding:10px;border-radius:3px}.well{border:none;box-shadow:none;background-color:#ededed;margin-bottom:1px}.well hr{margin:15px 0 10px;border-top:1px solid #d9d9d9}.well table>thead{font-size:11px}.tab-sub-menu{padding-left:10px}.tab-sub-menu li>a:hover,.tab-sub-menu li>a:focus{background-color:#f7f7f7;border-bottom-color:#ddd}.tab-sub-menu li.active>a{background-color:#fff;border-bottom-color:transparent}.tab-menu{padding-top:10px;background-color:#fff}.tab-menu .nav-tabs{padding-left:10px}.tab-menu .nav-tabs li>a{padding-top:12px;border-color:#ddd;border-bottom:1px solid #ddd;background-color:#f7f7f7;max-height:41px;outline:none}.tab-menu .nav-tabs li>a:hover,.tab-menu .nav-tabs li>a:focus{padding-top:10px;border-top:3px solid #ddd}.tab-menu .nav-tabs li>a:hover{background-color:#f7f7f7}.tab-menu .nav-tabs li.active>a,.tab-menu .nav-tabs li.active>a:hover{padding-top:10px;border-top:3px solid #6fdbe8}.tab-menu .nav-tabs li.active>a{background-color:#fff;border-bottom-color:transparent}ul.tab-menu{padding-top:10px;background-color:#fff;padding-left:10px}ul.tab-menu-settings li>a{padding-top:12px;border-color:#ddd;border-bottom:1px solid #ddd;background-color:#f7f7f7;max-height:41px;outline:none}ul.tab-menu-settings li>a:hover,ul.tab-menu-settings li>a:focus{padding-top:10px;border-top:3px solid #ddd !important}ul.tab-menu-settings li>a:hover{background-color:#f7f7f7}ul.tab-menu-settings li.active>a,ul.tab-menu-settings li.active>a:hover,ul.tab-menu-settings li.active>a:focus{padding-top:10px;border-top:3px solid #6fdbe8 !important}ul.tab-menu-settings li.active>a{background-color:#fff;border-bottom-color:transparent !important}.nav-pills .dropdown-menu,.nav-tabs .dropdown-menu,.account .dropdown-menu{background-color:#708fa0;border:none}.nav-pills .dropdown-menu li.divider,.nav-tabs .dropdown-menu li.divider,.account .dropdown-menu li.divider{background-color:#628394;border-bottom:none;margin:9px 1px !important}.nav-pills .dropdown-menu li,.nav-tabs .dropdown-menu li,.account .dropdown-menu li{border-left:3px solid #708fa0}.nav-pills .dropdown-menu li a,.nav-tabs .dropdown-menu li a,.account .dropdown-menu li a{color:white;font-weight:400;font-size:13px;padding:4px 15px}.nav-pills .dropdown-menu li a i,.nav-tabs .dropdown-menu li a i,.account .dropdown-menu li a i{margin-right:5px;font-size:14px;display:inline-block;width:14px}.nav-pills .dropdown-menu li a:hover,.nav-tabs .dropdown-menu li a:hover,.account .dropdown-menu li a:hover,.nav-pills .dropdown-menu li a:visited,.nav-tabs .dropdown-menu li a:visited,.account .dropdown-menu li a:visited,.nav-pills .dropdown-menu li a:hover,.nav-tabs .dropdown-menu li a:hover,.account .dropdown-menu li a:hover,.nav-pills .dropdown-menu li a:focus,.nav-tabs .dropdown-menu li a:focus,.account .dropdown-menu li a:focus{background:none}.nav-pills .dropdown-menu li:hover,.nav-tabs .dropdown-menu li:hover,.account .dropdown-menu li:hover,.nav-pills .dropdown-menu li.selected,.nav-tabs .dropdown-menu li.selected,.account .dropdown-menu li.selected{border-left:3px solid #6fdbe8;color:#fff !important;background-color:#628394 !important}.nav-pills.preferences .dropdown .dropdown-toggle{color:#bebebe}.nav-pills.preferences .dropdown.open .dropdown-toggle,.nav-pills.preferences .dropdown.open .dropdown-toggle:hover{background-color:#708fa0}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{background-color:#708fa0}.nav-tabs{margin-bottom:10px}.list-group a [class^="fa-"],.list-group a [class*=" fa-"]{display:inline-block;width:18px}.nav-pills.preferences{position:absolute;right:10px;top:10px}.nav-pills.preferences .dropdown .dropdown-toggle{padding:2px 10px}.nav-pills.preferences .dropdown.open .dropdown-toggle,.nav-pills.preferences .dropdown.open .dropdown-toggle:hover{color:white}.nav-tabs li{font-weight:600;font-size:12px}.tab-content .tab-pane a{color:#6fdbe8}.tab-content .tab-pane .form-group{margin-bottom:5px}.nav-tabs.tabs-center li{float:none;display:inline-block}.nav-tabs.tabs-small li>a{padding:5px 7px}.nav .caret,.nav .caret:hover,.nav .caret:active{border-top-color:#555;border-bottom-color:#555;height:6.928px}.nav li.dropdown>a:hover .caret,.nav li.dropdown>a:active .caret{border-top-color:#555;border-bottom-color:#555}.nav .open>a .caret,.nav .open>a:hover .caret,.nav .open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:#ededed;color:#555}.nav .open>a .caret,.nav .open>a:hover .caret,.nav .open>a:focus .caret{color:#555}@media (max-width:991px){.controls-header{text-align:left !important}}.btn{float:none;border:none;-webkit-box-shadow:none;box-shadow:none;-moz-box-shadow:none;background-image:none;text-shadow:none;border-radius:3px;outline:none !important;margin-bottom:0;font-size:14px;font-weight:600;padding:8px 16px}.input.btn{outline:none}.btn-lg{padding:16px 28px}.btn-sm{padding:4px 8px;font-size:12px}.btn-sm i{font-size:14px}.btn-xs{padding:1px 5px;font-size:12px}.btn-default{background:#ededed;color:#7a7a7a !important}.btn-default:hover,.btn-default:focus{background:#e8e8e8;text-decoration:none;color:#7a7a7a}.btn-default:active,.btn-default.active{outline:0;background:#e0e0e0}.btn-default[disabled],.btn-default.disabled{background:#f2f2f2}.btn-default[disabled]:hover,.btn-default.disabled:hover,.btn-default[disabled]:focus,.btn-default.disabled:focus{background:#f2f2f2}.btn-default[disabled]:active,.btn-default.disabled:active,.btn-default[disabled].active,.btn-default.disabled.active{background:#f2f2f2}.btn-primary{background:#708fa0;color:#fff !important}.btn-primary:hover,.btn-primary:focus{background:#628394;text-decoration:none}.btn-primary:active,.btn-primary.active{outline:0;background:#628394 !important}.btn-primary[disabled],.btn-primary.disabled{background:#7f9baa}.btn-primary[disabled]:hover,.btn-primary.disabled:hover,.btn-primary[disabled]:focus,.btn-primary.disabled:focus{background:#7f9baa}.btn-primary[disabled]:active,.btn-primary.disabled:active,.btn-primary[disabled].active,.btn-primary.disabled.active{background:#7f9baa !important}.btn-info{background:#6fdbe8;color:#fff !important}.btn-info:hover,.btn-info:focus{background:#59d6e4 !important;text-decoration:none}.btn-info:active,.btn-info.active{outline:0;background:#59d6e4}.btn-info[disabled],.btn-info.disabled{background:#85e0ec}.btn-info[disabled]:hover,.btn-info.disabled:hover,.btn-info[disabled]:focus,.btn-info.disabled:focus{background:#85e0ec}.btn-info[disabled]:active,.btn-info.disabled:active,.btn-info[disabled].active,.btn-info.disabled.active{background:#85e0ec !important}.btn-danger{background:#ff8989;color:#fff !important}.btn-danger:hover,.btn-danger:focus{background:#ff6f6f;text-decoration:none}.btn-danger:active,.btn-danger.active{outline:0;background:#ff6f6f !important}.btn-danger[disabled],.btn-danger.disabled{background:#ffa3a3}.btn-danger[disabled]:hover,.btn-danger.disabled:hover,.btn-danger[disabled]:focus,.btn-danger.disabled:focus{background:#ffa3a3}.btn-danger[disabled]:active,.btn-danger.disabled:active,.btn-danger[disabled].active,.btn-danger.disabled.active{background:#ffa3a3 !important}.btn-success{background:#97d271;color:#fff !important}.btn-success:hover,.btn-success:focus{background:#89cc5e;text-decoration:none}.btn-success:active,.btn-success.active{outline:0;background:#89cc5e !important}.btn-success[disabled],.btn-success.disabled{background:#a5d884}.btn-success[disabled]:hover,.btn-success.disabled:hover,.btn-success[disabled]:focus,.btn-success.disabled:focus{background:#a5d884}.btn-success[disabled]:active,.btn-success.disabled:active,.btn-success[disabled].active,.btn-success.disabled.active{background:#a5d884 !important}.btn-warning{background:#fdd198;color:#fff !important}.btn-warning:hover,.btn-warning:focus{background:#fdcd8e;text-decoration:none}.btn-warning:active,.btn-warning.active{outline:0;background:#fdcd8e !important}.btn-warning[disabled],.btn-warning.disabled{background:#fddcb1}.btn-warning[disabled]:hover,.btn-warning.disabled:hover,.btn-warning[disabled]:focus,.btn-warning.disabled:focus{background:#fddcb1}.btn-warning[disabled]:active,.btn-warning.disabled:active,.btn-warning[disabled].active,.btn-warning.disabled.active{background:#fddcb1 !important}.radio,.checkbox{margin-top:5px !important;margin-bottom:0}.radio label,.checkbox label{padding-left:10px}.form-control{border:2px solid #ededed;box-shadow:none;min-height:35px}.form-control:focus{border:2px solid #6fdbe8;outline:0;box-shadow:none}.form-control.form-search{border-radius:30px;background-image:url("../img/icon_search16x16.png");background-repeat:no-repeat;background-position:10px 8px;padding-left:34px}.form-group-search{position:relative}.form-group-search .form-button-search{position:absolute;top:4px;right:4px;border-radius:30px}textarea{resize:none;height:1.5em}select.form-control:not([multiple]){-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("../img/select_arrow.png") !important;background-repeat:no-repeat;background-position:right 13px;overflow:hidden}label{font-weight:normal}label.control-label{font-weight:bold}::-webkit-input-placeholder{color:#bebebe !important}::-moz-placeholder{color:#bebebe !important}:-ms-input-placeholder{color:#bebebe !important}input:-moz-placeholder{color:#bebebe !important}.placeholder{padding:10px}input.placeholder,textarea.placeholder{padding:0 0 0 10px;color:#999}.help-block-error{font-size:12px}.hint-block,.help-block:not(.help-block-error){color:#aeaeae !important;font-size:12px}.hint-block:hover,.help-block:not(.help-block-error):hover{color:#7a7a7a !important;font-size:12px}.input-group-addon{border:none}a.input-field-addon{font-size:12px;float:right;margin-top:-10px}a.input-field-addon-sm{font-size:11px;float:right;margin-top:-10px}.timeZoneInputContainer{padding-top:10px}.timeZoneInputContainer~.help-block{margin:0}.label{text-transform:uppercase}.label{text-transform:uppercase;display:inline-block;padding:3px 5px 4px;font-weight:600;font-size:10px !important;color:white !important;vertical-align:baseline;white-space:nowrap;text-shadow:none}.label-default{background:#ededed;color:#7a7a7a !important}a.label-default:hover{background:#e0e0e0 !important}.label-info{background-color:#6fdbe8}a.label-info:hover{background:#59d6e4 !important}.label-danger{background-color:#ff8989}a.label-danger:hover{background:#ff6f6f !important}.label-success{background-color:#97d271}a.label-success:hover{background:#89cc5e !important}.label-warning{background-color:#fdd198}a.label-warning:hover{background:#fdc67f !important}.regular-checkbox:checked+.regular-checkbox-box{border:2px solid #6fdbe8;background:#6fdbe8;color:white}.regular-checkbox-box.disabled{background:#d7d7d7 !important;border:2px solid #d7d7d7 !important;cursor:not-allowed}.regular-radio:checked+.regular-radio-button:after{background:#6fdbe8}.regular-radio:checked+.regular-radio-button{background-color:none;color:#99a1a7;border:2px solid #d7d7d7;margin-right:5px}.regular-radio.disabled{background:#d7d7d7 !important;border:2px solid #d7d7d7 !important;cursor:not-allowed}.errorMessage{color:#ff8989;padding:10px 0}.error{border-color:#ff8989 !important}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#ff8989 !important}.has-error .form-control,.has-error .form-control:focus{border-color:#ff8989;-webkit-box-shadow:none;box-shadow:none}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#97d271}.has-success .form-control,.has-success .form-control:focus{border-color:#97d271;-webkit-box-shadow:none;box-shadow:none}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#fdd198}.has-warning .form-control,.has-warning .form-control:focus{border-color:#fdd198;-webkit-box-shadow:none;box-shadow:none}.bootstrap-timepicker-widget .form-control{padding:0}#notification_overview_filter label{display:block}#notification_overview_list .img-space{position:absolute;top:25px;left:25px}@media (max-width:767px){.notifications{position:inherit !important;float:left !important}.notifications .dropdown-menu{width:300px !important;margin-left:0 !important}.notifications .dropdown-menu .arrow{margin-left:-142px !important}}.badge-space{margin-top:6px}.badge-space-chooser{padding:3px 5px;margin-left:1px}.badge{padding:3px 5px;border-radius:2px;font-weight:normal;font-family:Arial,sans-serif;font-size:10px !important;text-transform:uppercase;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:none;background-color:#d7d7d7;line-height:1}.popover{border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);-moz-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175)}.popover .popover-title{background:none;border-bottom:none;color:#555;font-weight:300;font-size:16px;padding:15px}.popover .popover-content{font-size:13px;padding:5px 15px;color:#555}.popover .popover-content a{color:#6fdbe8}.popover .popover-content img{max-width:100%}.popover .popover-navigation{padding:15px}.list-group-item{padding:6px 15px;border:none;border-width:0 !important;border-left:3px solid #fff !important;font-size:12px;font-weight:600}.list-group-item i{font-size:14px}a.list-group-item:hover,a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#555;background-color:#f7f7f7;border-left:3px solid #6fdbe8 !important}@media (max-width:991px){.list-group{margin-left:4px}.list-group-item{display:inline-block !important;border-radius:3px !important;margin:4px 0;margin-bottom:4px !important}.list-group-item{border:none !important}a.list-group-item:hover,a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border:none !important;background:#708fa0 !important;color:#fff !important}}@media screen and (max-width:768px){.modal-dialog{width:auto !important;padding-top:30px;padding-bottom:30px}}.modal-top{z-index:999999 !important}.modal{overflow-y:visible}.modal-dialog-extra-small{width:400px}.modal-dialog-small{width:500px}.modal-dialog-normal{width:600px}.modal-dialog-medium{width:768px}.modal-dialog-large{width:900px}@media screen and (max-width:920px){.modal-dialog-large{width:auto !important;padding-top:30px;padding-bottom:30px}}.modal{border:none}.modal h1,.modal h2,.modal h3,.modal h4,.modal h5{margin-top:20px;color:#555;font-weight:300}.modal h4.media-heading{margin-top:0}.modal-title{font-size:20px;font-weight:200;color:#555}.modal-dialog,.modal-content{min-width:150px}.modal-content{-webkit-border-radius:3px;-moz-border-radius:3px;box-shadow:0 2px 26px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.1);-webkit-box-shadow:0 2px 26px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.1);-moz-box-shadow:0 2px 26px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.1);border:none}.modal-content .modal-header{padding:20px 20px 0;border-bottom:none;text-align:center}.modal-content .modal-header .close{margin-top:2px;margin-right:5px}.modal-content .modal-body{padding:20px;font-size:13px}.modal-content .modal-footer{margin-top:0;text-align:left;padding:10px 20px 30px;border-top:none;text-align:center}.modal-content .modal-footer hr{margin-top:0}.modal-backdrop{background-color:rgba(0,0,0,0.5)}.modal-dialog.fadeIn,.modal-dialog.pulse{-webkit-animation-duration:200ms;-moz-animation-duration:200ms;animation-duration:200ms}.module-installed{opacity:.5}.module-installed .label-success{background-color:#d7d7d7}.tooltip-inner{background-color:#708fa0;max-width:400px;text-align:left;font-weight:300;padding:2px 8px 4px;font-weight:bold;white-space:pre-wrap}.tooltip.top .tooltip-arrow{border-top-color:#708fa0}.tooltip.top-left .tooltip-arrow{border-top-color:#708fa0}.tooltip.top-right .tooltip-arrow{border-top-color:#708fa0}.tooltip.right .tooltip-arrow{border-right-color:#708fa0}.tooltip.left .tooltip-arrow{border-left-color:#708fa0}.tooltip.bottom .tooltip-arrow{border-bottom-color:#708fa0}.tooltip.bottom-left .tooltip-arrow{border-bottom-color:#708fa0}.tooltip.bottom-right .tooltip-arrow{border-bottom-color:#708fa0}.tooltip.in{opacity:1;filter:alpha(opacity=100)}.progress{height:10px;margin-bottom:15px;box-shadow:none;background:#ededed;border-radius:10px}.progress-bar-info{background-color:#6fdbe8;-webkit-box-shadow:none;box-shadow:none}#nprogress .bar{height:2px;background:#6fdbe8}table{margin-bottom:0 !important}table th{font-size:11px;color:#bebebe;font-weight:normal}table thead tr th{border:none !important}table .time{font-size:12px}table td a:hover{color:#6fdbe8}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:10px 10px 10px 0}.table>thead>tr>th select,.table>tbody>tr>th select,.table>tfoot>tr>th select,.table>thead>tr>td select,.table>tbody>tr>td select,.table>tfoot>tr>td select{font-size:12px;padding:4px 8px;height:30px;margin:0}.table-middle>thead>tr>th,.table-middle>tbody>tr>th,.table-middle>tfoot>tr>th,.table-middle>thead>tr>td,.table-middle>tbody>tr>td,.table-middle>tfoot>tr>td{vertical-align:middle !important}.comment-container{margin-top:10px}.comment-container .wall-entry-controls{margin-left:50px}.comment .media{position:relative !important;margin-top:0}.comment .media .nav-pills.preferences{display:none;right:-3px;top:-3px}.comment .media-body{overflow:visible}.comment .jp-progress{background-color:#dbdcdd !important}.comment .jp-play-bar{background:#cacaca}.comment .content a{color:#21bdd0}.comment.guest-mode .media:last-child .wall-entry-controls{margin-bottom:0;margin-left:50px}.comment.guest-mode .media:last-child hr{display:none}.comment-container .content_edit{margin-left:50px}.comment_edit_content{margin-left:50px}.comment-message{overflow:hidden}.grid-view img{width:24px;height:24px}.grid-view .filters input,.grid-view .filters select{border:2px solid #ededed;box-shadow:none;min-height:35px;border-radius:4px;font-size:12px;padding:4px}.grid-view .filters input:focus,.grid-view .filters select:focus{border:2px solid #6fdbe8;outline:0;box-shadow:none}.grid-view{padding:15px 0 0}.grid-view img{border-radius:3px}.grid-view table th{font-size:13px !important;font-weight:bold !important}.grid-view table td{vertical-align:middle !important}.grid-view table tr{font-size:13px !important}.grid-view table thead tr th:first-of-type{padding-left:5px}.grid-view table tbody tr{height:50px}.grid-view table tbody tr td:first-of-type{padding-left:5px}.grid-view .summary{font-size:12px;color:#bac2c7}.permission-grid-editor>.table>tbody>tr:first-child>td{border:none}.permission-grid-editor{padding-top:0}.detail-view td,.detail-view th{padding:8px !important}.detail-view th{font-size:13px}.oembed_snippet{margin-top:10px;position:relative;padding-bottom:55%;padding-top:15px;height:0;overflow:hidden}.oembed_snippet iframe{position:absolute;top:0;left:0;width:100%;height:100%}.activities{max-height:400px;overflow:auto}.activities li .media{position:relative}.activities li .media .img-space{position:absolute;top:14px;left:14px}.activities li .media .media-body{max-width:295px}.contentForm_options{margin-top:10px;min-height:29px}.contentForm_options .btn_container{position:relative}.contentForm_options .btn_container .label-public{position:absolute;right:40px;top:11px}#contentFormError{color:#ff8989;padding-left:0;list-style:none}.placeholder-empty-stream{background-image:url("../img/placeholder-postform-arrow.png");background-repeat:no-repeat;padding:37px 0 0 70px;margin-left:90px}.wall-entry{position:relative}.wall-entry .content p,.wall-entry .content a{overflow:hidden;text-overflow:ellipsis;max-width:100%}.wall-entry .content img{max-width:100%}.wall-entry .media{overflow:visible}.wall-entry .well{margin-bottom:0}.wall-entry .well .comment .show-all-link{font-size:12px;cursor:pointer}.wall-entry .media-heading{font-size:14px;padding-top:1px;margin-bottom:3px}.wall-entry .media-heading .labels{padding-right:32px}.wall-entry .media-heading .viaLink{font-size:13px}.wall-entry .media-heading .viaLink i{color:#bebebe;padding-left:4px;padding-right:4px}.wall-entry .media-subheading{color:#bebebe;font-size:12px}.wall-entry .media-subheading .time{font-size:12px}.wall-entry-controls,.wall-entry-controls a{font-size:11px;font-weight:700;color:#7a7a7a;margin-top:10px;margin-bottom:0}.wallFilterPanel li{font-size:11px;font-weight:600}.wallFilterPanel li a{color:#555}.wallFilterPanel .dropdown-menu li{margin-bottom:0}.wallFilterPanel .dropdown-menu li a{font-size:12px}.wallFilterPanel .dropdown-menu li a:hover{color:#fff !important}.stream-entry-loader{float:right;margin-top:5px}.load-suppressed{margin-top:-10px;margin-bottom:15px;text-align:center}.load-suppressed a{display:inline-block;background-color:white;padding:5px;border-radius:4px;border:1px solid #ddd;font-size:11px}.space-owner{text-align:center;margin:14px 0;font-size:13px;color:#999}.space-member-sign{color:#97d271;position:absolute;top:42px;left:42px;font-size:16px;background:#fff;width:24px;height:24px;padding:2px 3px 1px 4px;border-radius:50px;border:2px solid #97d271}#space-menu-dropdown i.type{font-size:16px;color:#BFBFBF}#space-menu-spaces [data-space-chooser-item]{cursor:pointer}#space-menu-dropdown .input-group-addon{border-radius:0 4px 4px 0}#space-menu-dropdown .input-group-addon.focus{border-radius:0 4px 4px 0;border:2px solid #6fdbe8;border-left:0}#space-menu-search{border-right:0}#space-directory-link i{margin-right:0}.space-acronym{color:#fff;text-align:center;display:inline-block}.current-space-image{margin-right:3px;margin-top:3px}@media (max-width:767px){#space-menu>.title{display:none}#space-menu-dropdown{width:300px !important}}.files,#postFormFiles_list{padding-left:0}.contentForm-upload-list{padding-left:0}.contentForm-upload-list li:first-child{margin-top:10px}.file_upload_remove_link,.file_upload_remove_link:hover{color:#ff8989;cursor:pointer}.file-preview-item{text-overflow:ellipsis;overflow:hidden}.post-files{margin-top:10px}.post-files img{vertical-align:top;margin-bottom:3px;margin-right:5px;max-height:130px;-webkit-animation-duration:2s;animation-duration:2s}#wallStream.mobile .post-files{margin-top:10px;display:flex;overflow-x:auto}#wallStream.mobile .post-files img{max-width:190px}.comment_create,.content_edit{position:relative}.comment_create .comment-buttons,.content_edit .comment-buttons{position:absolute;top:2px;right:5px}.comment_create .btn-comment-submit,.content_edit .btn-comment-submit{margin-top:3px}.comment_create .fileinput-button,.content_edit .fileinput-button{float:left;padding:6px 10px;background:transparent !important}.comment_create .fileinput-button .fa,.content_edit .fileinput-button .fa{color:#d7d7d7}.comment_create .fileinput-button:hover .fa,.content_edit .fileinput-button:hover .fa{background:transparent !important;color:#b2b2b2}.comment_create .fileinput-button:active,.content_edit .fileinput-button:active{box-shadow:none !important}.file-preview-content{cursor:pointer}.image-upload-container{position:relative}.image-upload-container .image-upload-buttons{display:none;position:absolute;right:5px;bottom:5px}.image-upload-container input[type="file"]{position:absolute;opacity:0}.image-upload-container .image-upload-loader{display:none;position:absolute;top:0;left:0;width:100%;height:100%;padding:20px;background:#f8f8f8}.mime{background-repeat:no-repeat;background-position:0 0;padding:1px 0 4px 26px}.mime-word{background-image:url("../img/mime/word.png")}.mime-excel{background-image:url("../img/mime/excel.png")}.mime-powerpoint{background-image:url("../img/mime/powerpoint.png")}.mime-pdf{background-image:url("../img/mime/pdf.png")}.mime-zip{background-image:url("../img/mime/zip.png")}.mime-image{background-image:url("../img/mime/image.png")}.mime-file{background-image:url("../img/mime/file.png")}.mime-photoshop{background-image:url("../img/mime/photoshop.png")}.mime-illustrator{background-image:url("../img/mime/illustrator.png")}.mime-video{background-image:url("../img/mime/video.png")}.mime-audio{background-image:url("../img/mime/audio.png")}ul.tour-list{list-style:none;margin-bottom:0;padding-left:10px}ul.tour-list li{padding-top:5px}ul.tour-list li a{color:#6fdbe8}ul.tour-list li a .fa{width:16px}ul.tour-list li.completed a{text-decoration:line-through;color:#bebebe}.atwho-view .cur{border-left:3px solid #6fdbe8;background-color:#f7f7f7 !important}.atwho-user,.atwho-space,.atwho-input a{color:#6fdbe8}.atwho-input a:hover{color:#6fdbe8}.atwho-view strong{background-color:#f9f0d2}.atwho-view .cur strong{background-color:#f9f0d2}.sk-spinner-three-bounce.sk-spinner{margin:0 auto;width:70px;text-align:center}.loader{padding:30px 0}.loader .sk-spinner-three-bounce div,.loader .sk-spinner-three-bounce span{width:12px;height:12px;background-color:#6fdbe8;border-radius:100%;display:inline-block;-webkit-animation:sk-threeBounceDelay 1.4s infinite ease-in-out;animation:sk-threeBounceDelay 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.loader .sk-spinner-three-bounce .sk-bounce1{-webkit-animation-delay:-0.32s;animation-delay:-0.32s}.loader .sk-spinner-three-bounce .sk-bounce2{-webkit-animation-delay:-0.16s;animation-delay:-0.16s}@-webkit-keyframes sk-threeBounceDelay{0%,80%,100%{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes sk-threeBounceDelay{0%,80%,100%{-webkit-transform:scale(0);transform:scale(0)}40%{-webkit-transform:scale(1);transform:scale(1)}}.loader-modal{padding:8px 0}.loader-postform{padding:9px 0}.loader-postform .sk-spinner-three-bounce.sk-spinner{text-align:left;margin:0}.markdown-render h1,.markdown-render h2,.markdown-render h3,.markdown-render h4,.markdown-render h5,.markdown-render h6{font-weight:bold !important}.markdown-render h1{font-size:28px !important}.markdown-render h2{font-size:24px !important}.markdown-render h3{font-size:18px !important}.markdown-render h4{font-size:16px !important}.markdown-render h5{font-size:14px !important}.markdown-render h6{color:#999;font-size:14px !important}.markdown-render pre{padding:0;border:none;border-radius:3px}.markdown-render pre code{padding:10px;border-radius:3px;font-size:12px !important}.markdown-render a,.markdown-render a:visited{background-color:inherit;text-decoration:none;color:#6fdbe8 !important}.markdown-render img{max-width:100%}.markdown-render table{width:100%}.markdown-render table th{font-size:13px;font-weight:700;color:#555}.markdown-render table thead tr{border-bottom:1px solid #d7d7d7}.markdown-render table tbody tr td,.markdown-render table thead tr th{border:1px solid #d7d7d7 !important;padding:4px}.md-editor.active{border:2px solid #6fdbe8 !important}.md-editor textarea{padding:10px !important}[data-ui-markdown] h1,[data-ui-markdown] h2,[data-ui-markdown] h3,[data-ui-markdown] h4,[data-ui-markdown] h5,[data-ui-markdown] h6{text-align:start;margin:0 0 .5em}[data-ui-markdown] h1{font-size:1.7em !important;font-weight:600}[data-ui-markdown] h2{font-size:1.5em !important;font-weight:500}[data-ui-markdown] h3{font-size:1.2em !important}[data-ui-markdown] h4{font-size:1.1em !important}[data-ui-markdown] h5{font-size:1em !important}[data-ui-markdown] h6{font-size:.85em !important}[data-ui-markdown] p,[data-ui-markdown] pre,[data-ui-markdown] blockquote{margin:0 0 1.1em}[data-ui-markdown] p:last-child{margin:0}[data-ui-markdown] blockquote{border-left-width:10px;background-color:rgba(128,128,128,0.05);border-top-right-radius:5px;border-bottom-right-radius:5px;padding:15px 20px;font-size:1em;border-left:5px solid #888888}[data-ui-markdown] table{margin-bottom:20px;max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}[data-ui-markdown] table caption+thead tr:first-child th,[data-ui-markdown] table caption+thead tr:first-child td,[data-ui-markdown] table colgroup+thead tr:first-child th,[data-ui-markdown] table colgroup+thead tr:first-child td,[data-ui-markdown] table thead:first-child tr:first-child th,[data-ui-markdown] table thead:first-child tr:first-child td{border-top:0}[data-ui-markdown] table thead th{vertical-align:bottom}[data-ui-markdown] table th{font-weight:bold;text-align:left}[data-ui-markdown] table th,[data-ui-markdown] table td{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #ddd}[data-ui-markdown] dt,[data-ui-markdown] dd{margin-top:5px;margin-bottom:5px;line-height:1.45}[data-ui-markdown] dt{font-weight:bold}[data-ui-markdown] dd{margin-left:40px}[data-ui-markdown] pre{text-align:start;border:0;padding:10px 20px;border-radius:5px}[data-ui-markdown] pre code{white-space:pre !important}[data-ui-markdown] blockquote ul:last-child,[data-ui-markdown] blockquote ol:last-child{margin-bottom:0}[data-ui-markdown] ul,[data-ui-markdown] ol{margin-top:0;margin-bottom:10.5px}[data-ui-markdown] ul li p,[data-ui-markdown] ol li p{overflow:visible !important}[data-ui-markdown] .footnote{vertical-align:top;position:relative;top:-0.5em;font-size:.8em}blockquote{border-left:2px dotted #888;padding-left:5px;background:#d0f0ff}.wmd-panel{min-width:500px}.wmd-button-bar{width:100%;background-color:Silver}.wmd-input{height:300px;width:100%;background-color:Gainsboro;border:1px solid DarkGray}.wmd-button-row{position:relative;margin-left:5px;margin-right:5px;margin-bottom:5px;margin-top:10px;padding:0;height:20px}.wmd-spacer{width:1px;height:20px;margin-left:14px;position:absolute;background-color:Silver;display:inline-block;list-style:none}.wmd-button{width:20px;height:20px;padding-left:2px;padding-right:3px;position:absolute;display:inline-block;list-style:none;cursor:pointer}.wmd-button>span{background-image:url(../img/wmd-buttons.png);background-repeat:no-repeat;background-position:0 0;width:20px;height:20px;display:inline-block}.wmd-spacer1{left:50px}.wmd-spacer2{left:175px}.wmd-spacer3{left:300px}.wmd-prompt-background{background-color:Black}.wmd-prompt-dialog{border:1px solid #999999;background-color:#F5F5F5}.wmd-prompt-dialog>div{font-size:.8em;font-family:arial,helvetica,sans-serif}.wmd-prompt-dialog>form>input[type="text"]{border:1px solid #999999;color:black}.wmd-prompt-dialog>form>input[type="button"]{border:1px solid #888888;font-family:trebuchet MS,helvetica,sans-serif;font-size:.8em;font-weight:bold}@media (max-width:991px){.layout-sidebar-container{display:none}}.ui-widget-header{border:none !important;background:#fff !important;color:#7a7a7a !important;font-weight:300 !important}.ui-widget-content{border:1px solid #dddcda !important;border-radius:0 !important;background:#fff;color:#555 !important;-webkit-box-shadow:0 6px 6px rgba(0,0,0,0.1);box-shadow:0 6px 6px rgba(0,0,0,0.1)}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{opacity:.2}.ui-datepicker .ui-datepicker-prev:hover,.ui-datepicker .ui-datepicker-next:hover{background:#fff !important;border:none;margin:1px}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:none !important;background:#f7f7f7 !important;color:#7a7a7a !important}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:none !important;border:1px solid #b2b2b2 !important}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #6fdbe8 !important;background:#ddf6fa !important}.status-bar-body{color:white;position:fixed;width:100%;background-color:rgba(0,0,0,0.7);text-align:center;padding:20px;z-index:9999999;bottom:0;display:block;line-height:20px}.status-bar-close{color:white;fonfont-weight:bold;font-size:21px;cursor:pointer}.status-bar-close:hover{color:white}.status-bar-close i{vertical-align:top !important;padding-top:3px}.status-bar-content i{margin-right:10px;font-size:21px;vertical-align:middle}.status-bar-content .showMore{color:#6fdbe8;float:right;margin-left:10px;font-size:.7em;cursor:pointer;vertical-align:middle;white-space:nowrap}.status-bar-content .status-bar-details{text-align:left;font-size:.7em;margin-top:20px;max-height:200px;overflow:auto}.status-bar-content span{vertical-align:middle}.status-bar-content i.error,.status-bar-content i.fatal{color:#ff8989}.status-bar-content i.warning{color:#fdd198}.status-bar-content i.info,.status-bar-content i.debug{color:#6fdbe8}.status-bar-content i.success{color:#85CA2B}.highlight{background-color:#fff8e0}.alert-default{color:#555;background-color:#f7f7f7;border-color:#ededed;font-size:13px}.alert-default .info{margin:10px 0}.alert-success{color:#84be5e;background-color:#f7fbf4;border-color:#97d271}.alert-warning{color:#e9b168;background-color:#fffbf7;border-color:#fdd198}.alert-danger{color:#ff8989;background-color:#fff6f6;border-color:#ff8989}.data-saved{padding-left:10px;color:#6fdbe8}img.bounceIn{-webkit-animation-duration:800ms;-moz-animation-duration:800ms;animation-duration:800ms}.tags .tag{margin-top:5px;border-radius:2px;padding:4px 8px;text-transform:uppercase;max-width:150px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}/*! Select2 humhub Theme v0.1.0-beta.4 | MIT License | github.com/select2/select2-humhub-theme */.select2-container--humhub{display:block}.select2-container--humhub .select2-selection{background-color:#fff;border:2px solid #ededed;border-radius:4px;color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;outline:0}.select2-container--humhub .select2-search--dropdown .select2-search__field{background-color:#fff;border:2px solid #ededed;border-radius:4px;color:#555;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px}.select2-container--humhub .select2-search__field{outline:0}.select2-container--humhub .select2-search__field::-webkit-input-placeholder{color:#999}.select2-container--humhub .select2-search__field:-moz-placeholder{color:#999}.select2-container--humhub .select2-search__field::-moz-placeholder{color:#999;opacity:1}.select2-container--humhub .select2-search__field:-ms-input-placeholder{color:#999}.select2-container--humhub .select2-results__option[role=group]{padding:0}.select2-container--humhub .select2-results__option[aria-disabled=true]{color:#777;cursor:not-allowed}.select2-container--humhub .select2-results__option[aria-selected=true]{background-color:#f5f5f5;color:#262626;border-left:3px solid transparent}.select2-container--humhub .select2-results__option[aria-selected=false]{border-left:3px solid transparent}.select2-container--humhub .select2-results__option--highlighted[aria-selected]{background-color:#f7f7f7;border-left:3px solid #6fdbe8;color:#555}.select2-container--humhub .select2-results__option .select2-results__option{padding:6px 12px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option{margin-left:-12px;padding-left:24px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-24px;padding-left:36px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-36px;padding-left:48px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-48px;padding-left:60px}.select2-container--humhub .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-60px;padding-left:72px}.select2-container--humhub .select2-results__group{color:#777;display:block;padding:6px 12px;font-size:12px;line-height:1.42857143;white-space:nowrap}.select2-container--humhub.select2-container--focus .select2-selection,.select2-container--humhub.select2-container--open .select2-selection{border:2px solid #6fdbe8;outline:0;box-shadow:none}.select2-container--humhub.select2-container--open .select2-selection .select2-selection__arrow b{border-color:transparent transparent #999 transparent;border-width:0 4px 4px 4px}.select2-container--humhub .select2-selection__clear{color:#999;cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--humhub .select2-selection__clear:hover{color:#333}.select2-container--humhub.select2-container--disabled .select2-selection{border-color:#ccc;-webkit-box-shadow:none;box-shadow:none}.select2-container--humhub.select2-container--disabled .select2-selection,.select2-container--humhub.select2-container--disabled .select2-search__field{cursor:not-allowed}.select2-container--humhub.select2-container--disabled .select2-selection,.select2-container--humhub.select2-container--disabled .select2-selection--multiple .select2-selection__choice{background-color:#eee}.select2-container--humhub.select2-container--disabled .select2-selection__clear,.select2-container--humhub.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove{display:none}.select2-container--humhub .select2-dropdown{-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);border-color:#d7d7d7;overflow-x:hidden;margin-top:-1px}.select2-container--humhub .select2-dropdown--above{margin-top:1px}.select2-container--humhub .select2-results>.select2-results__options{max-height:400px;overflow-y:auto}.select2-container--humhub .select2-selection--single{height:34px;line-height:1.42857143;padding:6px 24px 6px 12px}.select2-container--humhub .select2-selection--single .select2-selection__arrow{position:absolute;bottom:0;right:12px;top:0;width:4px}.select2-container--humhub .select2-selection--single .select2-selection__arrow b{border-color:#999 transparent transparent transparent;border-style:solid;border-width:4px 4px 0 4px;height:0;left:0;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--humhub .select2-selection--single .select2-selection__rendered{color:#555;padding:0}.select2-container--humhub .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--humhub .select2-selection--multiple{min-height:34px;padding:2px}.select2-container--humhub .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;display:block;line-height:1.42857143;list-style:none;margin:0;overflow:hidden;padding:0;width:100%;text-overflow:ellipsis;white-space:nowrap}.select2-container--humhub .select2-selection--multiple .select2-selection__placeholder{color:#999;float:left;margin-top:5px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice{color:#555;border-radius:4px;cursor:default;padding:0 6px;background-color:#6fdbe8;color:#fff;border-radius:3px;font-size:12px !important;padding:2px 5px 2px 2px;float:left;margin:2px;height:28px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice img,.select2-container--humhub .select2-selection--multiple .select2-selection__choice div{margin-right:5px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice span.no-image{line-height:27px;padding-left:5px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice i{margin:0 2px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice .picker-close{cursor:pointer}.select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field{background:transparent;padding:0 5px;width:auto !important;height:32px;line-height:1.42857143;margin-top:0;min-width:5em}.select2-container--humhub .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:none;font-weight:bold;margin-right:3px}.select2-container--humhub .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--humhub .select2-selection--multiple .select2-selection__clear{margin-top:6px}.select2-container--humhub.input-sm,.select2-container--humhub.input-lg{border-radius:0;font-size:12px;height:auto;line-height:1;padding:0}.select2-container--humhub.input-sm .select2-selection--single,.input-group-sm .select2-container--humhub .select2-selection--single,.form-group-sm .select2-container--humhub .select2-selection--single{border-radius:3px;font-size:12px;height:30px;line-height:1.5;padding:5px 22px 5px 10px}.select2-container--humhub.input-sm .select2-selection--single .select2-selection__arrow b,.input-group-sm .select2-container--humhub .select2-selection--single .select2-selection__arrow b,.form-group-sm .select2-container--humhub .select2-selection--single .select2-selection__arrow b{margin-left:-5px}.select2-container--humhub.input-sm .select2-selection--multiple,.input-group-sm .select2-container--humhub .select2-selection--multiple,.form-group-sm .select2-container--humhub .select2-selection--multiple{min-height:30px}.select2-container--humhub.input-sm .select2-selection--multiple .select2-selection__choice,.input-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__choice,.form-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__choice{font-size:12px;line-height:1.5;margin:4px 0 0 5px;padding:0 5px}.select2-container--humhub.input-sm .select2-selection--multiple .select2-search--inline .select2-search__field,.input-group-sm .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field,.form-group-sm .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field{padding:0 10px;font-size:12px;height:28px;line-height:1.5}.select2-container--humhub.input-sm .select2-selection--multiple .select2-selection__clear,.input-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__clear,.form-group-sm .select2-container--humhub .select2-selection--multiple .select2-selection__clear{margin-top:5px}.select2-container--humhub.input-lg .select2-selection--single,.input-group-lg .select2-container--humhub .select2-selection--single,.form-group-lg .select2-container--humhub .select2-selection--single{border-radius:6px;font-size:18px;height:46px;line-height:1.3333333;padding:10px 31px 10px 16px}.select2-container--humhub.input-lg .select2-selection--single .select2-selection__arrow,.input-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow,.form-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow{width:5px}.select2-container--humhub.input-lg .select2-selection--single .select2-selection__arrow b,.input-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow b,.form-group-lg .select2-container--humhub .select2-selection--single .select2-selection__arrow b{border-width:5px 5px 0 5px;margin-left:-5px;margin-left:-10px;margin-top:-2.5px}.select2-container--humhub.input-lg .select2-selection--multiple,.input-group-lg .select2-container--humhub .select2-selection--multiple,.form-group-lg .select2-container--humhub .select2-selection--multiple{min-height:46px}.select2-container--humhub.input-lg .select2-selection--multiple .select2-selection__choice,.input-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__choice,.form-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__choice{font-size:18px;line-height:1.3333333;border-radius:4px;margin:9px 0 0 8px;padding:0 10px}.select2-container--humhub.input-lg .select2-selection--multiple .select2-search--inline .select2-search__field,.input-group-lg .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field,.form-group-lg .select2-container--humhub .select2-selection--multiple .select2-search--inline .select2-search__field{padding:0 16px;font-size:18px;height:44px;line-height:1.3333333}.select2-container--humhub.input-lg .select2-selection--multiple .select2-selection__clear,.input-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__clear,.form-group-lg .select2-container--humhub .select2-selection--multiple .select2-selection__clear{margin-top:10px}.select2-container--humhub.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #999 transparent;border-width:0 5px 5px 5px}.input-group-lg .select2-container--humhub.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #999 transparent;border-width:0 5px 5px 5px}.select2-container--humhub[dir="rtl"] .select2-selection--single{padding-left:24px;padding-right:12px}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:0;padding-left:0;text-align:right}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:12px;right:auto}.select2-container--humhub[dir="rtl"] .select2-selection--single .select2-selection__arrow b{margin-left:0}.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder{float:right}.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:0;margin-right:6px}.select2-container--humhub[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.has-warning .select2-dropdown,.has-warning .select2-selection{border-color:#fdd198}.has-warning .select2-container--focus .select2-selection,.has-warning .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fffefc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fffefc;border-color:#fcbb66}.has-warning.select2-drop-active{border-color:#fcbb66}.has-warning.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#fcbb66}.has-error .select2-dropdown,.has-error .select2-selection{border-color:#ff8989}.has-error .select2-container--focus .select2-selection,.has-error .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ffefef;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ffefef;border-color:#ff5656}.has-error.select2-drop-active{border-color:#ff5656}.has-error.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#ff5656}.has-success .select2-dropdown,.has-success .select2-selection{border-color:#97d271}.has-success .select2-container--focus .select2-selection,.has-success .select2-container--open .select2-selection{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d0ebbe;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d0ebbe;border-color:#7bc64a}.has-success.select2-drop-active{border-color:#7bc64a}.has-success.select2-drop-active.select2-drop.select2-drop-above{border-top-color:#7bc64a}.input-group .select2-container--humhub{display:table;table-layout:fixed;position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group.select2-humhub-prepend .select2-container--humhub .select2-selection{border-bottom-left-radius:0;border-top-left-radius:0}.input-group.select2-humhub-append .select2-container--humhub .select2-selection{border-bottom-right-radius:0;border-top-right-radius:0}.select2-humhub-append .select2-container--humhub,.select2-humhub-prepend .select2-container--humhub,.select2-humhub-append .input-group-btn,.select2-humhub-prepend .input-group-btn,.select2-humhub-append .input-group-btn .btn,.select2-humhub-prepend .input-group-btn .btn{vertical-align:top}.form-control.select2-hidden-accessible{position:absolute !important;width:1px !important}.form-inline .select2-container--humhub{display:inline-block}ul.tag_input{list-style:none;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;padding:0 0 9px 4px}ul.tag_input li img{margin:0 5px 0 0}.tag_input_field{outline:none;border:none !important;padding:5px 4px 0 !important;width:170px;margin:2px 0 0 !important}.userInput,.spaceInput{background-color:#6fdbe8;font-weight:600;color:#fff;border-radius:3px;font-size:12px !important;padding:2px;float:left;margin:3px 4px 0 0}.userInput i,.spaceInput i{padding:0 6px;font-size:14px;cursor:pointer;line-height:8px} \ No newline at end of file diff --git a/themes/HumHub/less/variables.less b/themes/HumHub/less/variables.less index f63f8a6824..d71d47aca0 100644 --- a/themes/HumHub/less/variables.less +++ b/themes/HumHub/less/variables.less @@ -1,7 +1,7 @@ /** * Define or overwrite your theme variables here. * - * Check for @humhub/less/variables.less file for all available variables. + * Check for @humhub/static/less/variables.less file for all available variables. * * You can also access your variables within your view files by calling Yii::$app->view->theme->variable('myVariable'); *