├── LICENSE ├── Procfile ├── app.json ├── composer.json ├── composer.lock ├── config ├── config.yml └── nginx.conf ├── install ├── install.php └── mysql.sql ├── public ├── .htaccess ├── cheryl.css ├── cheryl.js ├── cheryl.phtml ├── icon.png ├── index.php └── wink.svg ├── readme.md └── src ├── Cheryl.php └── User.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2003-2016 Devin Smith (http://devin.la) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: vendor/bin/heroku-php-nginx -C config/nginx.conf public/ 2 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cheryl", 3 | "description": "Web based file management for the modern web", 4 | "website": "http://cheryl.io", 5 | "repository": "https://github.com/arzynik/cheryl", 6 | "keywords": ["cheryl", "tipsy", "php", "file", "manager"], 7 | "addons": ["heroku-postgresql"], 8 | "scripts": { 9 | "postdeploy": "php install/install.php" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "tipsyphp/tipsy": "^0.11.6", 4 | "symfony/yaml": "^3.0" 5 | }, 6 | "require-dev": { 7 | "satooshi/php-coveralls": "dev-master" 8 | }, 9 | "autoload": { 10 | "psr-4": { 11 | "Cheryl\\": "src/" 12 | }, 13 | "files": [ 14 | ] 15 | }, 16 | "archive": { 17 | "exclude": ["/tests", "/.travis.yml", "/.gitignore"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "69f1c7b1475df47b8eff2d61d9dcbf4c", 8 | "content-hash": "11da3b374f3794c549eb6dd3c9d4c496", 9 | "packages": [ 10 | { 11 | "name": "symfony/yaml", 12 | "version": "v3.0.6", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/symfony/yaml.git", 16 | "reference": "0047c8366744a16de7516622c5b7355336afae96" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96", 21 | "reference": "0047c8366744a16de7516622c5b7355336afae96", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.5.9" 26 | }, 27 | "type": "library", 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "3.0-dev" 31 | } 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Symfony\\Component\\Yaml\\": "" 36 | }, 37 | "exclude-from-classmap": [ 38 | "/Tests/" 39 | ] 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Fabien Potencier", 48 | "email": "fabien@symfony.com" 49 | }, 50 | { 51 | "name": "Symfony Community", 52 | "homepage": "https://symfony.com/contributors" 53 | } 54 | ], 55 | "description": "Symfony Yaml Component", 56 | "homepage": "https://symfony.com", 57 | "time": "2016-03-04 07:55:57" 58 | }, 59 | { 60 | "name": "tipsyphp/tipsy", 61 | "version": "v0.11.8", 62 | "source": { 63 | "type": "git", 64 | "url": "https://github.com/tipsyphp/tipsy.git", 65 | "reference": "3dac011472cc22917fbb90930e70d9ff1e48800d" 66 | }, 67 | "dist": { 68 | "type": "zip", 69 | "url": "https://api.github.com/repos/tipsyphp/tipsy/zipball/3dac011472cc22917fbb90930e70d9ff1e48800d", 70 | "reference": "3dac011472cc22917fbb90930e70d9ff1e48800d", 71 | "shasum": "" 72 | }, 73 | "require": { 74 | "php": ">=5.5.0" 75 | }, 76 | "require-dev": { 77 | "satooshi/php-coveralls": "dev-master" 78 | }, 79 | "type": "library", 80 | "autoload": { 81 | "psr-4": { 82 | "Tipsy\\": "src/" 83 | }, 84 | "files": [ 85 | "src/Tipsy.php" 86 | ] 87 | }, 88 | "notification-url": "https://packagist.org/downloads/", 89 | "license": [ 90 | "MIT" 91 | ], 92 | "authors": [ 93 | { 94 | "name": "Devin Smith", 95 | "email": "devin@tipsy.la", 96 | "homepage": "http://devin.la", 97 | "role": "Developer" 98 | } 99 | ], 100 | "description": "An MVW PHP framework", 101 | "keywords": [ 102 | "framework", 103 | "mvc", 104 | "mvw", 105 | "php" 106 | ], 107 | "time": "2014-07-24 00:00:00" 108 | } 109 | ], 110 | "packages-dev": [ 111 | { 112 | "name": "guzzlehttp/guzzle", 113 | "version": "6.2.0", 114 | "source": { 115 | "type": "git", 116 | "url": "https://github.com/guzzle/guzzle.git", 117 | "reference": "d094e337976dff9d8e2424e8485872194e768662" 118 | }, 119 | "dist": { 120 | "type": "zip", 121 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", 122 | "reference": "d094e337976dff9d8e2424e8485872194e768662", 123 | "shasum": "" 124 | }, 125 | "require": { 126 | "guzzlehttp/promises": "~1.0", 127 | "guzzlehttp/psr7": "~1.1", 128 | "php": ">=5.5.0" 129 | }, 130 | "require-dev": { 131 | "ext-curl": "*", 132 | "phpunit/phpunit": "~4.0", 133 | "psr/log": "~1.0" 134 | }, 135 | "type": "library", 136 | "extra": { 137 | "branch-alias": { 138 | "dev-master": "6.2-dev" 139 | } 140 | }, 141 | "autoload": { 142 | "files": [ 143 | "src/functions_include.php" 144 | ], 145 | "psr-4": { 146 | "GuzzleHttp\\": "src/" 147 | } 148 | }, 149 | "notification-url": "https://packagist.org/downloads/", 150 | "license": [ 151 | "MIT" 152 | ], 153 | "authors": [ 154 | { 155 | "name": "Michael Dowling", 156 | "email": "mtdowling@gmail.com", 157 | "homepage": "https://github.com/mtdowling" 158 | } 159 | ], 160 | "description": "Guzzle is a PHP HTTP client library", 161 | "homepage": "http://guzzlephp.org/", 162 | "keywords": [ 163 | "client", 164 | "curl", 165 | "framework", 166 | "http", 167 | "http client", 168 | "rest", 169 | "web service" 170 | ], 171 | "time": "2016-03-21 20:02:09" 172 | }, 173 | { 174 | "name": "guzzlehttp/promises", 175 | "version": "1.2.0", 176 | "source": { 177 | "type": "git", 178 | "url": "https://github.com/guzzle/promises.git", 179 | "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579" 180 | }, 181 | "dist": { 182 | "type": "zip", 183 | "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579", 184 | "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579", 185 | "shasum": "" 186 | }, 187 | "require": { 188 | "php": ">=5.5.0" 189 | }, 190 | "require-dev": { 191 | "phpunit/phpunit": "~4.0" 192 | }, 193 | "type": "library", 194 | "extra": { 195 | "branch-alias": { 196 | "dev-master": "1.0-dev" 197 | } 198 | }, 199 | "autoload": { 200 | "psr-4": { 201 | "GuzzleHttp\\Promise\\": "src/" 202 | }, 203 | "files": [ 204 | "src/functions_include.php" 205 | ] 206 | }, 207 | "notification-url": "https://packagist.org/downloads/", 208 | "license": [ 209 | "MIT" 210 | ], 211 | "authors": [ 212 | { 213 | "name": "Michael Dowling", 214 | "email": "mtdowling@gmail.com", 215 | "homepage": "https://github.com/mtdowling" 216 | } 217 | ], 218 | "description": "Guzzle promises library", 219 | "keywords": [ 220 | "promise" 221 | ], 222 | "time": "2016-05-18 16:56:05" 223 | }, 224 | { 225 | "name": "guzzlehttp/psr7", 226 | "version": "1.3.0", 227 | "source": { 228 | "type": "git", 229 | "url": "https://github.com/guzzle/psr7.git", 230 | "reference": "31382fef2889136415751badebbd1cb022a4ed72" 231 | }, 232 | "dist": { 233 | "type": "zip", 234 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/31382fef2889136415751badebbd1cb022a4ed72", 235 | "reference": "31382fef2889136415751badebbd1cb022a4ed72", 236 | "shasum": "" 237 | }, 238 | "require": { 239 | "php": ">=5.4.0", 240 | "psr/http-message": "~1.0" 241 | }, 242 | "provide": { 243 | "psr/http-message-implementation": "1.0" 244 | }, 245 | "require-dev": { 246 | "phpunit/phpunit": "~4.0" 247 | }, 248 | "type": "library", 249 | "extra": { 250 | "branch-alias": { 251 | "dev-master": "1.0-dev" 252 | } 253 | }, 254 | "autoload": { 255 | "psr-4": { 256 | "GuzzleHttp\\Psr7\\": "src/" 257 | }, 258 | "files": [ 259 | "src/functions_include.php" 260 | ] 261 | }, 262 | "notification-url": "https://packagist.org/downloads/", 263 | "license": [ 264 | "MIT" 265 | ], 266 | "authors": [ 267 | { 268 | "name": "Michael Dowling", 269 | "email": "mtdowling@gmail.com", 270 | "homepage": "https://github.com/mtdowling" 271 | } 272 | ], 273 | "description": "PSR-7 message implementation", 274 | "keywords": [ 275 | "http", 276 | "message", 277 | "stream", 278 | "uri" 279 | ], 280 | "time": "2016-04-13 19:56:01" 281 | }, 282 | { 283 | "name": "psr/http-message", 284 | "version": "1.0", 285 | "source": { 286 | "type": "git", 287 | "url": "https://github.com/php-fig/http-message.git", 288 | "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" 289 | }, 290 | "dist": { 291 | "type": "zip", 292 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", 293 | "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", 294 | "shasum": "" 295 | }, 296 | "require": { 297 | "php": ">=5.3.0" 298 | }, 299 | "type": "library", 300 | "extra": { 301 | "branch-alias": { 302 | "dev-master": "1.0.x-dev" 303 | } 304 | }, 305 | "autoload": { 306 | "psr-4": { 307 | "Psr\\Http\\Message\\": "src/" 308 | } 309 | }, 310 | "notification-url": "https://packagist.org/downloads/", 311 | "license": [ 312 | "MIT" 313 | ], 314 | "authors": [ 315 | { 316 | "name": "PHP-FIG", 317 | "homepage": "http://www.php-fig.org/" 318 | } 319 | ], 320 | "description": "Common interface for HTTP messages", 321 | "keywords": [ 322 | "http", 323 | "http-message", 324 | "psr", 325 | "psr-7", 326 | "request", 327 | "response" 328 | ], 329 | "time": "2015-05-04 20:22:00" 330 | }, 331 | { 332 | "name": "psr/log", 333 | "version": "1.0.0", 334 | "source": { 335 | "type": "git", 336 | "url": "https://github.com/php-fig/log.git", 337 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" 338 | }, 339 | "dist": { 340 | "type": "zip", 341 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", 342 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", 343 | "shasum": "" 344 | }, 345 | "type": "library", 346 | "autoload": { 347 | "psr-0": { 348 | "Psr\\Log\\": "" 349 | } 350 | }, 351 | "notification-url": "https://packagist.org/downloads/", 352 | "license": [ 353 | "MIT" 354 | ], 355 | "authors": [ 356 | { 357 | "name": "PHP-FIG", 358 | "homepage": "http://www.php-fig.org/" 359 | } 360 | ], 361 | "description": "Common interface for logging libraries", 362 | "keywords": [ 363 | "log", 364 | "psr", 365 | "psr-3" 366 | ], 367 | "time": "2012-12-21 11:40:51" 368 | }, 369 | { 370 | "name": "satooshi/php-coveralls", 371 | "version": "dev-master", 372 | "source": { 373 | "type": "git", 374 | "url": "https://github.com/satooshi/php-coveralls.git", 375 | "reference": "50c60bb64054974f8ed7540ae6e75fd7981a5fd3" 376 | }, 377 | "dist": { 378 | "type": "zip", 379 | "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/50c60bb64054974f8ed7540ae6e75fd7981a5fd3", 380 | "reference": "50c60bb64054974f8ed7540ae6e75fd7981a5fd3", 381 | "shasum": "" 382 | }, 383 | "require": { 384 | "ext-json": "*", 385 | "ext-simplexml": "*", 386 | "guzzlehttp/guzzle": "^6.0", 387 | "php": ">=5.5", 388 | "psr/log": "^1.0", 389 | "symfony/config": "^2.1|^3.0", 390 | "symfony/console": "^2.1|^3.0", 391 | "symfony/stopwatch": "^2.0|^3.0", 392 | "symfony/yaml": "^2.0|^3.0" 393 | }, 394 | "suggest": { 395 | "symfony/http-kernel": "Allows Symfony integration" 396 | }, 397 | "bin": [ 398 | "bin/coveralls" 399 | ], 400 | "type": "library", 401 | "extra": { 402 | "branch-alias": { 403 | "dev-master": "2.0-dev" 404 | } 405 | }, 406 | "autoload": { 407 | "psr-4": { 408 | "Satooshi\\": "src/Satooshi/" 409 | } 410 | }, 411 | "notification-url": "https://packagist.org/downloads/", 412 | "license": [ 413 | "MIT" 414 | ], 415 | "authors": [ 416 | { 417 | "name": "Kitamura Satoshi", 418 | "email": "with.no.parachute@gmail.com", 419 | "homepage": "https://www.facebook.com/satooshi.jp" 420 | } 421 | ], 422 | "description": "PHP client library for Coveralls API", 423 | "homepage": "https://github.com/satooshi/php-coveralls", 424 | "keywords": [ 425 | "ci", 426 | "coverage", 427 | "github", 428 | "test" 429 | ], 430 | "time": "2016-01-20 17:44:41" 431 | }, 432 | { 433 | "name": "symfony/config", 434 | "version": "v3.0.6", 435 | "source": { 436 | "type": "git", 437 | "url": "https://github.com/symfony/config.git", 438 | "reference": "24f155da1ff180df8e15e34a8f6e2f8a0eadefa8" 439 | }, 440 | "dist": { 441 | "type": "zip", 442 | "url": "https://api.github.com/repos/symfony/config/zipball/24f155da1ff180df8e15e34a8f6e2f8a0eadefa8", 443 | "reference": "24f155da1ff180df8e15e34a8f6e2f8a0eadefa8", 444 | "shasum": "" 445 | }, 446 | "require": { 447 | "php": ">=5.5.9", 448 | "symfony/filesystem": "~2.8|~3.0" 449 | }, 450 | "suggest": { 451 | "symfony/yaml": "To use the yaml reference dumper" 452 | }, 453 | "type": "library", 454 | "extra": { 455 | "branch-alias": { 456 | "dev-master": "3.0-dev" 457 | } 458 | }, 459 | "autoload": { 460 | "psr-4": { 461 | "Symfony\\Component\\Config\\": "" 462 | }, 463 | "exclude-from-classmap": [ 464 | "/Tests/" 465 | ] 466 | }, 467 | "notification-url": "https://packagist.org/downloads/", 468 | "license": [ 469 | "MIT" 470 | ], 471 | "authors": [ 472 | { 473 | "name": "Fabien Potencier", 474 | "email": "fabien@symfony.com" 475 | }, 476 | { 477 | "name": "Symfony Community", 478 | "homepage": "https://symfony.com/contributors" 479 | } 480 | ], 481 | "description": "Symfony Config Component", 482 | "homepage": "https://symfony.com", 483 | "time": "2016-04-20 18:53:54" 484 | }, 485 | { 486 | "name": "symfony/console", 487 | "version": "v3.0.6", 488 | "source": { 489 | "type": "git", 490 | "url": "https://github.com/symfony/console.git", 491 | "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820" 492 | }, 493 | "dist": { 494 | "type": "zip", 495 | "url": "https://api.github.com/repos/symfony/console/zipball/34a214710e0714b6efcf40ba3cd1e31373a97820", 496 | "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820", 497 | "shasum": "" 498 | }, 499 | "require": { 500 | "php": ">=5.5.9", 501 | "symfony/polyfill-mbstring": "~1.0" 502 | }, 503 | "require-dev": { 504 | "psr/log": "~1.0", 505 | "symfony/event-dispatcher": "~2.8|~3.0", 506 | "symfony/process": "~2.8|~3.0" 507 | }, 508 | "suggest": { 509 | "psr/log": "For using the console logger", 510 | "symfony/event-dispatcher": "", 511 | "symfony/process": "" 512 | }, 513 | "type": "library", 514 | "extra": { 515 | "branch-alias": { 516 | "dev-master": "3.0-dev" 517 | } 518 | }, 519 | "autoload": { 520 | "psr-4": { 521 | "Symfony\\Component\\Console\\": "" 522 | }, 523 | "exclude-from-classmap": [ 524 | "/Tests/" 525 | ] 526 | }, 527 | "notification-url": "https://packagist.org/downloads/", 528 | "license": [ 529 | "MIT" 530 | ], 531 | "authors": [ 532 | { 533 | "name": "Fabien Potencier", 534 | "email": "fabien@symfony.com" 535 | }, 536 | { 537 | "name": "Symfony Community", 538 | "homepage": "https://symfony.com/contributors" 539 | } 540 | ], 541 | "description": "Symfony Console Component", 542 | "homepage": "https://symfony.com", 543 | "time": "2016-04-28 09:48:42" 544 | }, 545 | { 546 | "name": "symfony/filesystem", 547 | "version": "v3.0.6", 548 | "source": { 549 | "type": "git", 550 | "url": "https://github.com/symfony/filesystem.git", 551 | "reference": "74fec3511b62cb934b64bce1d96f06fffa4beafd" 552 | }, 553 | "dist": { 554 | "type": "zip", 555 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/74fec3511b62cb934b64bce1d96f06fffa4beafd", 556 | "reference": "74fec3511b62cb934b64bce1d96f06fffa4beafd", 557 | "shasum": "" 558 | }, 559 | "require": { 560 | "php": ">=5.5.9" 561 | }, 562 | "type": "library", 563 | "extra": { 564 | "branch-alias": { 565 | "dev-master": "3.0-dev" 566 | } 567 | }, 568 | "autoload": { 569 | "psr-4": { 570 | "Symfony\\Component\\Filesystem\\": "" 571 | }, 572 | "exclude-from-classmap": [ 573 | "/Tests/" 574 | ] 575 | }, 576 | "notification-url": "https://packagist.org/downloads/", 577 | "license": [ 578 | "MIT" 579 | ], 580 | "authors": [ 581 | { 582 | "name": "Fabien Potencier", 583 | "email": "fabien@symfony.com" 584 | }, 585 | { 586 | "name": "Symfony Community", 587 | "homepage": "https://symfony.com/contributors" 588 | } 589 | ], 590 | "description": "Symfony Filesystem Component", 591 | "homepage": "https://symfony.com", 592 | "time": "2016-04-12 18:09:53" 593 | }, 594 | { 595 | "name": "symfony/polyfill-mbstring", 596 | "version": "v1.2.0", 597 | "source": { 598 | "type": "git", 599 | "url": "https://github.com/symfony/polyfill-mbstring.git", 600 | "reference": "dff51f72b0706335131b00a7f49606168c582594" 601 | }, 602 | "dist": { 603 | "type": "zip", 604 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", 605 | "reference": "dff51f72b0706335131b00a7f49606168c582594", 606 | "shasum": "" 607 | }, 608 | "require": { 609 | "php": ">=5.3.3" 610 | }, 611 | "suggest": { 612 | "ext-mbstring": "For best performance" 613 | }, 614 | "type": "library", 615 | "extra": { 616 | "branch-alias": { 617 | "dev-master": "1.2-dev" 618 | } 619 | }, 620 | "autoload": { 621 | "psr-4": { 622 | "Symfony\\Polyfill\\Mbstring\\": "" 623 | }, 624 | "files": [ 625 | "bootstrap.php" 626 | ] 627 | }, 628 | "notification-url": "https://packagist.org/downloads/", 629 | "license": [ 630 | "MIT" 631 | ], 632 | "authors": [ 633 | { 634 | "name": "Nicolas Grekas", 635 | "email": "p@tchwork.com" 636 | }, 637 | { 638 | "name": "Symfony Community", 639 | "homepage": "https://symfony.com/contributors" 640 | } 641 | ], 642 | "description": "Symfony polyfill for the Mbstring extension", 643 | "homepage": "https://symfony.com", 644 | "keywords": [ 645 | "compatibility", 646 | "mbstring", 647 | "polyfill", 648 | "portable", 649 | "shim" 650 | ], 651 | "time": "2016-05-18 14:26:46" 652 | }, 653 | { 654 | "name": "symfony/stopwatch", 655 | "version": "v3.0.6", 656 | "source": { 657 | "type": "git", 658 | "url": "https://github.com/symfony/stopwatch.git", 659 | "reference": "6015187088421e9499d8f8316bdb396f8b806c06" 660 | }, 661 | "dist": { 662 | "type": "zip", 663 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6015187088421e9499d8f8316bdb396f8b806c06", 664 | "reference": "6015187088421e9499d8f8316bdb396f8b806c06", 665 | "shasum": "" 666 | }, 667 | "require": { 668 | "php": ">=5.5.9" 669 | }, 670 | "type": "library", 671 | "extra": { 672 | "branch-alias": { 673 | "dev-master": "3.0-dev" 674 | } 675 | }, 676 | "autoload": { 677 | "psr-4": { 678 | "Symfony\\Component\\Stopwatch\\": "" 679 | }, 680 | "exclude-from-classmap": [ 681 | "/Tests/" 682 | ] 683 | }, 684 | "notification-url": "https://packagist.org/downloads/", 685 | "license": [ 686 | "MIT" 687 | ], 688 | "authors": [ 689 | { 690 | "name": "Fabien Potencier", 691 | "email": "fabien@symfony.com" 692 | }, 693 | { 694 | "name": "Symfony Community", 695 | "homepage": "https://symfony.com/contributors" 696 | } 697 | ], 698 | "description": "Symfony Stopwatch Component", 699 | "homepage": "https://symfony.com", 700 | "time": "2016-03-04 07:55:57" 701 | } 702 | ], 703 | "aliases": [], 704 | "minimum-stability": "stable", 705 | "stability-flags": { 706 | "satooshi/php-coveralls": 20 707 | }, 708 | "prefer-stable": false, 709 | "prefer-lowest": false, 710 | "platform": [], 711 | "platform-dev": [] 712 | } 713 | -------------------------------------------------------------------------------- /config/config.yml: -------------------------------------------------------------------------------- 1 | view: 2 | path: ./ 3 | layout: 4 | 5 | cheryl: 6 | root: ../files 7 | CHANGE_ME_FOR_PDO__authentication: pdo 8 | authentication: simple 9 | storage: local 10 | users: 11 | - username: admin 12 | password_hash: $2y$10$LQhNZMW3E66NPcTUkCPDyenq3Pn5mJJyZmIVRWzOrIP/2c7HrtodC 13 | permissions: all 14 | 15 | db: 16 | host: localhost 17 | user: root 18 | pass: root 19 | database: cheryl 20 | -------------------------------------------------------------------------------- /config/nginx.conf: -------------------------------------------------------------------------------- 1 | client_max_body_size 16M; 2 | 3 | location / { 4 | # try to serve file directly, fallback to rewrite 5 | try_files $uri @rewriteapp; 6 | } 7 | 8 | location @rewriteapp { 9 | # rewrite all to app.php 10 | rewrite ^(.*)$ /index.php last; 11 | } 12 | -------------------------------------------------------------------------------- /install/install.php: -------------------------------------------------------------------------------- 1 | setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 11 | $db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); 12 | $db->exec($sql); 13 | -------------------------------------------------------------------------------- /install/mysql.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `user`; 2 | 3 | CREATE TABLE `user` ( 4 | `id_user` int(11) unsigned NOT NULL AUTO_INCREMENT, 5 | `username` varchar(40) DEFAULT NULL, 6 | `password_hash` varchar(255) DEFAULT NULL, 7 | PRIMARY KEY (`id_user`), 8 | UNIQUE KEY `username` (`username`) 9 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 10 | 11 | INSERT INTO `user` (`id_user`, `username`, `password_hash`) 12 | VALUES 13 | (1,'admin','$2y$10$LQhNZMW3E66NPcTUkCPDyenq3Pn5mJJyZmIVRWzOrIP/2c7HrtodC'); 14 | 15 | 16 | 17 | DROP TABLE IF EXISTS `permission`; 18 | 19 | CREATE TABLE `permission` ( 20 | `id_user_permission` int(11) unsigned NOT NULL AUTO_INCREMENT, 21 | `permission` varchar(40) DEFAULT NULL, 22 | `allow` tinyint(1) NOT NULL DEFAULT '1', 23 | `id_user` int(10) unsigned DEFAULT NULL, 24 | PRIMARY KEY (`id_user_permission`), 25 | KEY `id_user` (`id_user`), 26 | CONSTRAINT `permission_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id_user`) ON DELETE CASCADE ON UPDATE CASCADE 27 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 28 | 29 | INSERT INTO `permission` (`id_user_permission`, `id_user`, `permission`, `allow`) 30 | VALUES 31 | (1,1,'all',1); 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | allow from all 2 | 3 | RewriteEngine on 4 | Options +Followsymlinks -Indexes 5 | 6 | ReWriteCond %{REQUEST_METHOD} ^(TRACE|TRACK) 7 | ReWriteRule .* - [F] 8 | 9 | RewriteCond %{REQUEST_FILENAME} !-f 10 | RewriteCond %{REQUEST_FILENAME} !-d 11 | RewriteRule ^(.+)$ index.php?__url=$1 [L,QSA] 12 | 13 | AddDefaultCharset UTF-8 14 | -------------------------------------------------------------------------------- /public/cheryl.css: -------------------------------------------------------------------------------- 1 | html, body, margin, form, a, h1, h2, h3, h4, h5, h6, select, input, tr, td, table, ul, ol, li, textarea, p, button { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | body { 6 | background: #eceff1; 7 | color: #404040; 8 | background: #eceff1 -webkit-gradient(linear, left top, left bottom, color-stop(0%,#48aed9), color-stop(1000%,#48aed9)); 9 | background-size: 100% 142px; 10 | background-repeat:no-repeat; 11 | } 12 | body, input, button { 13 | font: 10px "Open Sans", "Helvetica", "Arial", sans-serif; 14 | } 15 | :focus { 16 | outline: none; 17 | } 18 | ::selection { 19 | background: rgba(255,39,151,.75); 20 | color: #fff; 21 | } 22 | ::-moz-selection { 23 | background: rgba(255,39,151,.75); 24 | color: #fff; 25 | } 26 | a, a:visited, a:active { 27 | text-decoration: none; 28 | color: #404040; 29 | } 30 | .wrapper { 31 | width: 870px; 32 | margin: 0 auto; 33 | margin-bottom: 25px; 34 | } 35 | .clearfix:after { 36 | content: ''; 37 | display: block; 38 | clear: both; 39 | visibility: hidden; 40 | line-height: 0; 41 | height: 0; 42 | } 43 | 44 | .logo { 45 | height: 90px; 46 | width: 90px; 47 | margin: 30px 0 40px 40px; 48 | background: url(./wink.svg) no-repeat; 49 | background-size: 100%; 50 | } 51 | .panel { 52 | float: left; 53 | width: 170px; 54 | margin: 0 20px 0 0; 55 | } 56 | .toggles { 57 | float: left; 58 | width: 680px; 59 | margin: 20px 0 0 0; 60 | } 61 | .content { 62 | float: left; 63 | width: 630px; 64 | min-height: 500px; 65 | background: #fff; 66 | box-shadow: 0px 1px 4px rgba(0,0,0,.3); 67 | padding: 25px; 68 | margin: 25px 0 20px 0; 69 | } 70 | h1 { 71 | color: #555963; 72 | font-weight: 100; 73 | font-size: 2em; 74 | } 75 | h2 { 76 | color: #404040; 77 | font-weight: 100; 78 | font-size: 1.5em; 79 | margin-bottom: .5em; 80 | } 81 | h1 input, h2 input { 82 | margin: 0; 83 | padding: 0; 84 | border: 1px dashed #f6f6f6; 85 | width: 20em; 86 | color: #555963; 87 | font-weight: 100; 88 | font-size: 1em; 89 | -webkit-transition: .2s border; 90 | } 91 | h2 input { 92 | border: 1px dashed #d0d0d0; 93 | padding: .3em; 94 | } 95 | .file-name-top, h1 input { 96 | padding-left: .2em; 97 | } 98 | h1 input:hover { 99 | border: 1px dashed #d0d0d0; 100 | } 101 | h1 input:focus, h2 input:focus { 102 | border: 1px dashed #b0b0b0; 103 | } 104 | .file-name-top { 105 | border: 1px solid transparent; 106 | } 107 | .toggles button { 108 | border: 0; 109 | background: rgba(0,0,0,.2); 110 | border-radius: 5px; 111 | color: #fff; 112 | font-size: 1.1em; 113 | padding: .5em 1.2em .6em 1.2em; 114 | cursor: pointer; 115 | opacity: .5; 116 | -webkit-transition: .2s opacity; 117 | width: 12em; 118 | } 119 | .toggles button.enabled { 120 | opacity: .8; 121 | } 122 | .toggles button:hover { 123 | opacity: .7; 124 | } 125 | .toggles button.enabled:hover { 126 | opacity: 1; 127 | } 128 | .toggles .search { 129 | border: 0; 130 | background: rgba(0,0,0,.2); 131 | border-radius: 5px; 132 | color: #fff; 133 | font-size: 1.1em; 134 | padding: .2em .4em .2em .8em; 135 | opacity: .5; 136 | -webkit-transition: .2s opacity; 137 | float: right; 138 | } 139 | .toggles .search .fa { 140 | vertical-align: middle; 141 | } 142 | .toggles .search .search-box { 143 | background: 0; 144 | padding: .35em .4em .45em .4em; 145 | border: 0; 146 | color: #fff; 147 | width: 8em; 148 | font-size: 1em; 149 | margin: 0; 150 | vertical-align: middle; 151 | -webkit-transition: .2s width; 152 | } 153 | .toggles .search.active { 154 | opacity: .8; 155 | } 156 | .toggles .search.active .search-box { 157 | width: 15em; 158 | } 159 | 160 | .toggles .search .search-box::-webkit-input-placeholder { 161 | color: #fff; 162 | -webkit-transition: .2s opacity; 163 | } 164 | .toggles .search .search-box::-moz-placeholder { 165 | color: #fff; 166 | -moz-transition: .2s opacity; 167 | } 168 | .toggles .search .search-box::-ms-input-placeholder { 169 | color: #fff; 170 | -ms-transition: .2s opacity; 171 | } 172 | .toggles .search .search-box::-o-input-placeholder { 173 | color: #fff; 174 | -o-transition: .2s opacity; 175 | } 176 | .toggles .search .search-box::input-placeholder { 177 | color: #fff; 178 | transition: .2s opacity; 179 | } 180 | 181 | .toggles .search.active .search-box::-webkit-input-placeholder { 182 | opacity: 0; 183 | } 184 | .toggles .search.active .search-box::-moz-placeholder { 185 | opacity: 0; 186 | } 187 | .toggles .search.active .search-box::-ms-placeholder { 188 | opacity: 0; 189 | } 190 | .toggles .search.active .search-box::-o-input-placeholder { 191 | opacity: 0; 192 | } 193 | .toggles .search.active .search-box::input-placeholder { 194 | opacity: 0; 195 | } 196 | 197 | .copyright { 198 | clear: both; 199 | float: right; 200 | cursor: pointer; 201 | } 202 | .copyright a { 203 | text-decoration: none; 204 | color: #cacccd; 205 | font-size: 2em; 206 | } 207 | .powered, .cheryl { 208 | -webkit-transition: .4s all; 209 | } 210 | .powered { 211 | opacity: .3; 212 | } 213 | .copyright:hover .powered { 214 | opacity: .7; 215 | } 216 | .copyright:hover .cheryl { 217 | color: #f83ca2; 218 | } 219 | .details .info { 220 | color: #767676; 221 | border-radius: 4px; 222 | padding: .2em 0 .2em 0; 223 | font-size: 1.2em; 224 | list-style: none; 225 | } 226 | .filter { 227 | background: #e0e2e4; 228 | color: #767676; 229 | display: block; 230 | border-radius: 4px; 231 | padding: .4em .8em .4em .8em; 232 | margin-bottom: .2em; 233 | font-size: 1.2em; 234 | cursor: pointer; 235 | } 236 | .filter.enabled { 237 | background: #a4d4e9; 238 | color: #fff; 239 | } 240 | .file .icon { 241 | font-size: 4em; 242 | color: #c0c0c0; 243 | width: 1em; 244 | height: 1em; 245 | display: inline-block; 246 | vertical-align: top; 247 | } 248 | .file .icon .fa { 249 | -webkit-transition: .2s all; 250 | position: absolute; 251 | display: block; 252 | } 253 | .file .icon .fa-cloud-upload { 254 | font-size: .85em; 255 | padding-top: .2em; 256 | } 257 | .file .icon .fa-download { 258 | opacity: 0; 259 | font-size: .9em; 260 | padding-top: .2em; 261 | } 262 | .file { 263 | background: #f0f0f0; 264 | padding: .8em 1.4em .8em 1.4em; 265 | cursor: pointer; 266 | -webkit-transition: .2s all; 267 | margin-bottom: .8em; 268 | display: block; 269 | border: 1px solid #f0f0f0; 270 | } 271 | .files .file:hover { 272 | background: #ecf3f9; 273 | } 274 | .files .file .icon:hover .fa-file-text-o { 275 | opacity: 0; 276 | } 277 | .files .file .icon:hover .fa-download { 278 | opacity: 1; 279 | } 280 | .files .file:focus { 281 | background: #ecf3f9; 282 | border: 1px solid #e0e6ec; 283 | } 284 | .files .file:hover .icon { 285 | color: #b5b5b5; 286 | } 287 | .uploads { 288 | margin-top: 1.4em; 289 | } 290 | .files { 291 | margin-top: 1.4em; 292 | } 293 | .file .attrs { 294 | display: inline-block; 295 | color: #9ea0a4; 296 | font-size: 1.1em; 297 | text-align: right; 298 | line-height: 1.7em; 299 | margin-top: .3em; 300 | float: right; 301 | } 302 | .file .filename { 303 | color: #303238; 304 | font-size: 17px; 305 | font-weight: 600; 306 | overflow: hidden; 307 | text-overflow: ellipsis; 308 | display: inline-block; 309 | white-space: nowrap; 310 | max-width: 100%; 311 | } 312 | .file .path { 313 | color: #9ea0a4; 314 | font-size: 1.1em; 315 | line-height: 1.6em; 316 | } 317 | .file .fileinfo { 318 | display: inline-block; 319 | margin-left: 1.5em; 320 | max-width: 70%; 321 | } 322 | .item-count { 323 | float: right; 324 | opacity: .7; 325 | } 326 | button, .filter { 327 | -webkit-touch-callout: none; 328 | -webkit-user-select: none; 329 | -khtml-user-select: none; 330 | -moz-user-select: none; 331 | -ms-user-select: none; 332 | -o-user-select: none; 333 | user-select: none; 334 | } 335 | .file-image { 336 | background-size: contain !important; 337 | background-repeat: no-repeat !important; 338 | width: 500px; 339 | height: 500px; 340 | } 341 | #downloader { 342 | display: none; 343 | } 344 | 345 | .upload { 346 | visibility: hidden; 347 | width: 0; 348 | height: 0; 349 | } 350 | .actions { 351 | float: right; 352 | text-align: right; 353 | margin-top: -4.3em; 354 | } 355 | .actions button { 356 | background: #555963; 357 | border: 1px solid #555963; 358 | border-radius: 4px; 359 | color: #fff; 360 | font-size: 1.4em; 361 | padding: .2em .4em .2em .4em; 362 | width: 2.7em; 363 | cursor: pointer; 364 | -webkit-transition: .15s all; 365 | margin-right: .3em; 366 | } 367 | .actions button:hover { 368 | background: #48aed9; 369 | color: #fff; 370 | border: 1px solid #48aed9; 371 | } 372 | .location .path { 373 | display: inline-block; 374 | margin-left: 4.1em; 375 | } 376 | 377 | 378 | @-webkit-keyframes progress-bar-stripes { 379 | from { 380 | background-position: 40px 0; 381 | } 382 | to { 383 | background-position: 0 0; 384 | } 385 | } 386 | 387 | @-moz-keyframes progress-bar-stripes { 388 | from { 389 | background-position: 40px 0; 390 | } 391 | to { 392 | background-position: 0 0; 393 | } 394 | } 395 | 396 | @-o-keyframes progress-bar-stripes { 397 | from { 398 | background-position: 40px 0; 399 | } 400 | to { 401 | background-position: 0 0; 402 | } 403 | } 404 | 405 | @keyframes progress-bar-stripes { 406 | from { 407 | background-position: 40px 0; 408 | } 409 | to { 410 | background-position: 0 0; 411 | } 412 | } 413 | 414 | .progress { 415 | height: 5.8em; 416 | overflow: hidden; 417 | background-color: #555963; 418 | float: left; 419 | width: 100%; 420 | } 421 | .progress-bar { 422 | overflow: hidden; 423 | height: 100%; 424 | width: 100%; 425 | background-color: #428bca; 426 | -webkit-transition: width 0.6s ease; 427 | -moz-transition: width 0.6s ease; 428 | -ms-transition: width 0.6s ease; 429 | -o-transition: width 0.6s ease; 430 | transition: width 0.6s ease; 431 | } 432 | .progress-striped .progress-bar { 433 | background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); 434 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 435 | background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 436 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 437 | background-size: 40px 40px; 438 | } 439 | .progress.active .progress-bar { 440 | -webkit-animation: progress-bar-stripes 2s linear infinite; 441 | -moz-animation: progress-bar-stripes 2s linear infinite; 442 | -ms-animation: progress-bar-stripes 2s linear infinite; 443 | -o-animation: progress-bar-stripes 2s linear infinite; 444 | animation: progress-bar-stripes 2s linear infinite; 445 | } 446 | .progress-bar-success { 447 | background-color: #5cb85c; 448 | } 449 | .progress-bar-info { 450 | background-color: #5bc0de; 451 | } 452 | .progress-bar-danger { 453 | background-color: #d9534f; 454 | } 455 | 456 | 457 | .upload-item { 458 | padding: 0; 459 | } 460 | .upload-content { 461 | padding: .8em 1.4em .8em 1.4em; 462 | float: left; 463 | margin-top: -5.8em; 464 | width: 95%; 465 | } 466 | .upload-content .filename, .upload-content .icon, .upload-content .attrs, .upload-content .path { 467 | color: #fff; 468 | } 469 | 470 | .modal-wrap { 471 | opacity: 0; 472 | pointer-events: none; 473 | -webkit-transition: .2s opacity; 474 | position: fixed; 475 | bottom: 0; 476 | height: 0; 477 | width: 0; 478 | z-index: 1000; 479 | } 480 | 481 | .modal-enabled { 482 | overflow: hidden; 483 | } 484 | 485 | .login-wrap, .modal-enabled .modal-wrap { 486 | position: fixed; 487 | width: 100%; 488 | height: 100%; 489 | top: 0; 490 | left: 0; 491 | right: 0; 492 | bottom: 0; 493 | overflow: hidden; 494 | } 495 | 496 | .login-wrap { 497 | background: #46aad5; 498 | } 499 | 500 | .modal-wrap { 501 | background: rgba(34,53,62,.7); 502 | } 503 | 504 | .modal-enabled .modal-wrap { 505 | opacity: 1; 506 | pointer-events: auto; 507 | } 508 | 509 | .login .welcome { 510 | color: #fff; 511 | font-size: 4.5em; 512 | font-weight: 100; 513 | text-align: center; 514 | margin-bottom: .8em; 515 | } 516 | 517 | .login .input-wrap { 518 | color: #fff; 519 | border-radius: 5px; 520 | background: rgba(0,0,0,.26); 521 | font-size: 2em; 522 | padding: .4em .6em .4em .6em; 523 | width: 100%; 524 | box-sizing: border-box; 525 | font-weight: 100; 526 | margin-bottom: .27em; 527 | } 528 | 529 | .login input { 530 | width: 100%; 531 | font-size: 1em; 532 | font-weight: 100; 533 | border: 0; 534 | background: none; 535 | color: #fff; 536 | } 537 | .login .field, .login .label { 538 | display: table-cell; 539 | } 540 | .login .field { 541 | width: 100%; 542 | } 543 | .login .label { 544 | min-width: 5.5em; 545 | opacity: .7; 546 | } 547 | 548 | .login { 549 | width: 35em; 550 | margin: 0 auto; 551 | margin-top: 10em; 552 | } 553 | 554 | .login-button { 555 | border-radius: 5px; 556 | background: rgba(0,0,0,.76); 557 | padding: .4em .6em .4em .6em; 558 | width: 100%; 559 | box-sizing: border-box; 560 | border: none; 561 | font-size: 2em; 562 | color: #fff; 563 | cursor: pointer; 564 | -webkit-transition: .2s all; 565 | } 566 | 567 | .copyright-login { 568 | opacity: .2; 569 | font-size: 2em; 570 | margin-top: 2em; 571 | text-align: right; 572 | -webkit-transition: .4s all; 573 | } 574 | .copyright-login:hover { 575 | opacity: .7; 576 | } 577 | .copyright-login:hover .cheryl { 578 | color: #f83ca2; 579 | } 580 | 581 | .filter-name { 582 | max-width: 2em; 583 | text-overflow:ellipsis; 584 | display: inline-block; 585 | overflow: hidden; 586 | max-width: 10em; 587 | height: 1.3em; 588 | vertical-align: top; 589 | } 590 | .modal { 591 | background: #fff; 592 | width: 36em; 593 | margin: 0 auto; 594 | margin-top: 20em; 595 | padding: 2em; 596 | min-height: 11em; 597 | text-align: center; 598 | box-shadow: 0px 0px 0px 4px rgba(0,0,0,.14); 599 | } 600 | 601 | .modal-dropupload { 602 | background: none; 603 | border-radius: 10px; 604 | border: 2px dashed #fff; 605 | box-shadow: none; 606 | width: 28em; 607 | color: #fff; 608 | } 609 | 610 | .modal-dropupload h1 { 611 | color: #fff; 612 | font-size: 9em; 613 | margin-top: .4em; 614 | line-height: 1em; 615 | } 616 | .modal-dropupload h2 { 617 | color: #fff; 618 | font-size: 2em; 619 | margin-bottom: 1.6em; 620 | } 621 | 622 | .modal button { 623 | border: none; 624 | padding: .45em 2em .45em 2em; 625 | font-size: 1.4em; 626 | margin: 0 .5em 0 .5em; 627 | cursor: pointer; 628 | border-radius: 3px; 629 | background: #555963; 630 | color: #fff; 631 | border: 1px solid #9ea1a7; 632 | -webkit-transition: .15s all; 633 | } 634 | 635 | .modal button:focus, .modal button:hover { 636 | background: #48aed9; 637 | color: #fff; 638 | border: 1px solid #48aed9; 639 | } 640 | 641 | @-webkit-keyframes shake { 642 | 0% { -webkit-transform: translate(2px, 1px) rotate(0deg); } 643 | 10% { -webkit-transform: translate(-1px, -2px) rotate(-1deg); } 644 | 20% { -webkit-transform: translate(-3px, 0px) rotate(1deg); } 645 | 30% { -webkit-transform: translate(0px, 2px) rotate(0deg); } 646 | 40% { -webkit-transform: translate(1px, -1px) rotate(1deg); } 647 | 50% { -webkit-transform: translate(-1px, 1px) rotate(-1deg); } 648 | 60% { -webkit-transform: translate(-3px, -2px) rotate(0deg); } 649 | 70% { -webkit-transform: translate(2px, 1px) rotate(-1deg); } 650 | 80% { -webkit-transform: translate(-1px, -2px) rotate(1deg); } 651 | 90% { -webkit-transform: translate(2px, -1px) rotate(0deg); } 652 | 100% { -webkit-transform: translate(1px, -2px) rotate(-1deg); } 653 | } 654 | 655 | .global-error .logo { 656 | -webkit-animation-name: 'shake'; 657 | -webkit-animation-duration: 0.3s; 658 | -webkit-transform-origin:50% 50%; 659 | -webkit-animation-iteration-count: 3; 660 | -webkit-animation-timing-function: linear; 661 | } 662 | #editor { 663 | height: 100%; 664 | min-height: 500px; 665 | font-size: 11px; 666 | 667 | } 668 | .file-contents { 669 | margin-top: 1em; 670 | } 671 | 672 | 673 | .fullscreen-editor #editor { 674 | height: auto !important; 675 | width: auto !important; 676 | border: 0; 677 | margin: 0; 678 | position: fixed !important; 679 | top: 0; 680 | bottom: 0; 681 | left: 0; 682 | right: 0; 683 | z-index: 10000 684 | } 685 | .fullscreen-editor { 686 | overflow: hidden 687 | } 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | .animated { 699 | -webkit-animation-fill-mode: both; 700 | -moz-animation-fill-mode: both; 701 | -ms-animation-fill-mode: both; 702 | -o-animation-fill-mode: both; 703 | animation-fill-mode: both; 704 | 705 | -webkit-animation-duration: 1s; 706 | -moz-animation-duration: 1s; 707 | -ms-animation-duration: 1s; 708 | -o-animation-duration: 1s; 709 | animation-duration: 1s; 710 | } 711 | .welcome { 712 | -webkit-animation-delay: .4s; 713 | -moz-animation-delay: .4s; 714 | -ms-animation-delay: .4s; 715 | -o-animation-delay: .4s; 716 | animation-delay: .4s; 717 | } 718 | .login-form { 719 | -webkit-animation-delay: 1.1s; 720 | -moz-animation-delay: 1.1s; 721 | -ms-animation-delay: 1.1s; 722 | -o-animation-delay: 1.1s; 723 | animation-delay: 1.1s; 724 | } 725 | @-webkit-keyframes fadeIn { 726 | 0% { 727 | opacity: 0; 728 | } 729 | 100% { 730 | opacity: 1; 731 | } 732 | } 733 | @-moz-keyframes fadeIn { 734 | 0% { 735 | opacity: 0; 736 | } 737 | 100% { 738 | opacity: 1; 739 | } 740 | } 741 | @-ms-keyframes fadeIn { 742 | 0% { 743 | opacity: 0; 744 | } 745 | 100% { 746 | opacity: 1; 747 | } 748 | } 749 | @-o-keyframes fadeIn { 750 | 0% { 751 | opacity: 0; 752 | } 753 | 100% { 754 | opacity: 1; 755 | } 756 | } 757 | @keyframes fadeIn { 758 | 0% { 759 | opacity: 0; 760 | } 761 | 100% { 762 | opacity: 1; 763 | } 764 | } 765 | @-webkit-keyframes fadeInDown { 766 | 0% { 767 | opacity: 0; 768 | -webkit-transform: translateY(-20px); 769 | } 770 | 100% { 771 | opacity: 1; 772 | -webkit-transform: translateY(0); 773 | } 774 | } 775 | 776 | @-moz-keyframes fadeInDown { 777 | 0% { 778 | opacity: 0; 779 | -moz-transform: translateY(-20px); 780 | } 781 | 100% { 782 | opacity: 1; 783 | -moz-transform: translateY(0); 784 | } 785 | } 786 | 787 | @-ms-keyframes fadeInDown { 788 | 0% { 789 | opacity: 0; 790 | -ms-transform: translateY(-20px); 791 | } 792 | 100% { 793 | opacity: 1; 794 | -ms-transform: translateY(0); 795 | } 796 | } 797 | 798 | @-o-keyframes fadeInDown { 799 | 0% { 800 | opacity: 0; 801 | -o-transform: translateY(-20px); 802 | } 803 | 100% { 804 | opacity: 1; 805 | -o-transform: translateY(0); 806 | } 807 | } 808 | 809 | @keyframes fadeInDown { 810 | 0% { 811 | opacity: 0; 812 | transform: translateY(-20px); 813 | } 814 | 100% { 815 | opacity: 1; 816 | transform: translateY(0); 817 | } 818 | } 819 | 820 | .fadeInDown { 821 | -webkit-animation-name: fadeInDown; 822 | -moz-animation-name: fadeInDown; 823 | -ms-animation-name: fadeInDown; 824 | -o-animation-name: fadeInDown; 825 | animation-name: fadeInDown; 826 | } 827 | .fadeIn { 828 | -webkit-animation-name: fadeIn; 829 | -moz-animation-name: fadeIn; 830 | -ms-animation-name: fadeIn; 831 | -o-animation-name: fadeIn; 832 | animation-name: fadeIn; 833 | } 834 | 835 | .preview img { 836 | width: 100%; 837 | } 838 | -------------------------------------------------------------------------------- /public/cheryl.js: -------------------------------------------------------------------------------- 1 | var editor; 2 | 3 | var Cheryl = 4 | angular.module('Cheryl', ['ngRoute']) 5 | .config(function($routeProvider){ 6 | $routeProvider 7 | .when('/logout', { 8 | action: 'logout', 9 | controller: 'LogoutCtrl', 10 | }) 11 | .when('/login', { 12 | action: 'login', 13 | controller: 'LoginCtrl', 14 | }) 15 | .otherwise({ 16 | action: 'home', 17 | controller: 'RootCtrl' 18 | }); 19 | }) 20 | .config(function($locationProvider){ 21 | $locationProvider.html5Mode(true).hashPrefix('!'); 22 | }) 23 | .controller('RootCtrl', function ($scope, $http, $location, $anchorScroll, $route) { 24 | $scope.now = new Date; 25 | $scope.yesterday = new Date; 26 | $scope.yesterday.setDate($scope.yesterday.getDate() - 1); 27 | 28 | $scope.user = []; 29 | $scope.welcomeDefault = $scope.welcome = 'Welcome!'; 30 | 31 | $scope.types = []; 32 | $scope.dates = []; 33 | $scope.type = 'dir'; 34 | $scope.dialog = false; 35 | $scope.config = false; 36 | 37 | $scope.dateFormat = 'm/d/Y H:i:s'; 38 | 39 | $scope.path = function() { 40 | return location.href + $scope.script; 41 | }; 42 | 43 | $scope.dirPath = function() { 44 | return $location.path().replace($scope.script,'') || '/'; 45 | }; 46 | 47 | $scope.getConfig = function() { 48 | $http({method: 'GET', url: $scope.path() + '?__p=config'}). 49 | success(function(data) { 50 | if (data.status) { 51 | $scope.authed = data.authed; 52 | $scope.config = true; 53 | } 54 | }); 55 | }; 56 | 57 | $scope.login = function() { 58 | $http.post($scope.path(), {'__p': 'login', '__username': $scope.user.username, '__password': $scope.user.password}). 59 | success(function(data) { 60 | if (data.status) { 61 | $scope.welcome = $scope.welcomeDefault; 62 | $scope.authed = true; 63 | $scope.user.password = ''; 64 | } else { 65 | $scope.welcome = 'Try again!'; 66 | } 67 | }). 68 | error(function(data) { 69 | $scope.welcome = 'Try again!'; 70 | }); 71 | }; 72 | 73 | $scope.authed = false; 74 | $scope.script = ''; 75 | 76 | $scope.dateFilterNames = { 77 | 0: 'Today', 78 | 1: 'Last week', 79 | 2: 'Last month', 80 | 3: 'Archive' 81 | }; 82 | 83 | $scope.filters = { 84 | recursive: 0, 85 | types: [], 86 | dates: [], 87 | search: '' 88 | }; 89 | 90 | $scope.uploads = []; 91 | 92 | $scope.filter = function(filter, value) { 93 | switch (filter) { 94 | case 'recursive': 95 | $scope.filters[filter] = value; 96 | break; 97 | case 'types': 98 | case 'dates': 99 | $scope.filters[filter][value] = !$scope.filters[filter][value]; 100 | var hasValue = false; 101 | for (var x in $scope.filters[filter]) { 102 | if ($scope.filters[filter][x]) { 103 | hasValue = true; 104 | break; 105 | } 106 | } 107 | if (!hasValue) { 108 | $scope.filters[filter] = []; 109 | } 110 | 111 | break; 112 | } 113 | }; 114 | 115 | $scope.filterFiles = function(file) { 116 | if (Object.size($scope.filters.types)) { 117 | if (!$scope.filters.types[file.ext.toUpperCase()]) { 118 | return false; 119 | } 120 | } 121 | if (Object.size($scope.filters.dates)) { 122 | if (!$scope.filters.dates[file.dateFilter]) { 123 | return false; 124 | } 125 | } 126 | if ($scope.filters.search && file.name.indexOf($scope.filters.search) === -1) { 127 | return false; 128 | } 129 | return true; 130 | }; 131 | 132 | $scope.filterFolders = function(file) { 133 | if ($scope.filters.search && file.name.indexOf($scope.filters.search) === -1) { 134 | return false; 135 | } 136 | return true; 137 | }; 138 | 139 | $scope.filterCheck = function(filter, value) { 140 | return $scope.filters[filter][value]; 141 | }; 142 | 143 | var formatDate = function(file) { 144 | var time = new Date(file.mtime * 1000); 145 | var timeOfDay = (time.getHours() > 12 ? time.getHours() - 12 : time.getHours()) + ':' + time.getMinutes() + (time.getHours() > 12 ? ' PM' : ' AM'); 146 | var daysAgo = Math.ceil(($scope.now.getTime() / 1000 / 60 / 60 / 24) - (time.getTime() / 1000 / 60 / 60 / 24)); 147 | 148 | if ('' + time.getFullYear() + time.getMonth() + time.getDate() == '' + $scope.now.getFullYear() + $scope.now.getMonth() + $scope.now.getDate()) { 149 | file.dateReadable = 'Today @ ' + timeOfDay; 150 | file.dateFilter = 0; 151 | 152 | } else if ('' + time.getFullYear() + time.getMonth() + time.getDate() == '' + $scope.yesterday.getFullYear() + $scope.yesterday.getMonth() + $scope.yesterday.getDate()) { 153 | file.dateReadable = 'Yesterday @ ' + timeOfDay; 154 | file.dateFilter = 1; 155 | 156 | } else if (daysAgo < 7) { 157 | file.dateReadable = daysAgo + ' day' + (daysAgo == 1 ? '' : 's') + ' ago'; 158 | file.dateFilter = 1; 159 | 160 | } else if (daysAgo < 28) { 161 | var weeks = Math.floor(daysAgo / 7); 162 | file.dateReadable = weeks + ' week' + (weeks == 1 ? '' : 's') + ' ago'; 163 | file.dateFilter = 2; 164 | 165 | } else if (daysAgo < 363) { 166 | var months = Math.floor(daysAgo / 30); 167 | file.dateReadable = months + ' month' + (months == 1 ? '' : 's') + ' ago'; 168 | file.dateFilter = 3; 169 | 170 | } else { 171 | var years = Math.floor(daysAgo / 365); 172 | file.dateReadable = years + ' year' + (years == 1 ? '' : 's') + ' ago'; 173 | file.dateFilter = 3; 174 | } 175 | 176 | $scope.dates[file.dateFilter] = $scope.dates[file.dateFilter] ? $scope.dates[file.dateFilter] + 1 : 1; 177 | }; 178 | 179 | $scope.formatSize = function(file) { 180 | var size = file.size; 181 | var i = -1; 182 | var byteUnits = [' KB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB']; 183 | do { 184 | size = size / 1024; 185 | i++; 186 | } while (size > 1024); 187 | file.sizeReadable = Math.max(size, 0.1).toFixed(1) + byteUnits[i]; 188 | }; 189 | 190 | $scope.loadFiles = function() { 191 | if (!$scope.config) { 192 | $scope.getConfig(); 193 | return; 194 | } 195 | if (!$scope.authed) { 196 | return; 197 | } 198 | $scope.fullscreenEdit = false; 199 | $scope.filters.search = ''; 200 | 201 | var url = $scope.path() + '?__p=ls&_d=' + $scope.dirPath(); 202 | for (var x in $scope.filters) { 203 | url += '&filters[' + x + ']=' + $scope.filters[x]; 204 | } 205 | 206 | $http({method: 'GET', url: url}). 207 | success(function(data) { 208 | $scope.type = data.type; 209 | $scope.file = data.file; 210 | $scope.file.nameUser = $scope.file.name; 211 | 212 | if (data.type == 'dir') { 213 | $scope.files = data.list.files; 214 | $scope.dirs = data.list.dirs; 215 | 216 | $scope.types = {}; 217 | $scope.dates = {}; 218 | 219 | for (var x in $scope.files) { 220 | var type = $scope.files[x].ext.toUpperCase(); 221 | $scope.types[type] = $scope.types[type] ? $scope.types[type]+1 : 1; 222 | formatDate($scope.files[x]); 223 | $scope.formatSize($scope.files[x]); 224 | } 225 | } else { 226 | formatDate($scope.file); 227 | $scope.formatSize($scope.file); 228 | 229 | } 230 | }). 231 | error(function() { 232 | $scope.files = null; 233 | }); 234 | }; 235 | 236 | $scope.$watch('filters.recursive', $scope.loadFiles); 237 | $scope.$watch('authed', $scope.loadFiles); 238 | $scope.$on('$locationChangeSuccess', function() { 239 | $anchorScroll(); 240 | 241 | switch ($location.$$hash) { 242 | case 'NewFile': 243 | break; 244 | 245 | default: 246 | $scope.loadFiles(); 247 | break; 248 | } 249 | }); 250 | 251 | $scope.modes = { 252 | php: 'php', 253 | js: 'javascript', 254 | css: 'css', 255 | md: 'markdown', 256 | svg: 'svg', 257 | xml: 'xml', 258 | asp: 'asp', 259 | c: 'c', 260 | sql: 'mysql' 261 | }; 262 | 263 | $scope.$watch('file.contents', function() { 264 | if (!$scope.file || !$scope.file.contents) { 265 | return; 266 | } 267 | 268 | if (!$scope.editor) { 269 | $scope.editor = ace.edit('editor'); 270 | $scope.editor.renderer.setShowPrintMargin(false); 271 | $scope.editor.session.setWrapLimitRange(null, null); 272 | 273 | $scope.editor.commands.addCommand({ 274 | name: 'saveFile', 275 | bindKey: { 276 | win: 'Ctrl-S', 277 | mac: 'Command-S', 278 | sender: 'editor|cli' 279 | }, 280 | exec: function(env, args, request) { 281 | $scope.saveFile(); 282 | } 283 | }); 284 | } 285 | 286 | var mode = 'text'; 287 | for (var x in $scope.modes) { 288 | if (x == $scope.file.ext) { 289 | mode = $scope.modes[x]; 290 | break; 291 | } 292 | } 293 | 294 | $scope.editor.getSession().setMode('ace/mode/' + mode); 295 | 296 | $scope.editor.getSession().setValue($scope.file.contents); 297 | $scope.editor.setReadOnly(!$scope.file.writeable); 298 | 299 | fullscreenToggle(); 300 | }); 301 | 302 | var fullscreenToggle = function() { 303 | if (!$scope.editor) { 304 | return; 305 | } 306 | if ($scope.fullscreenEdit) { 307 | $scope.editor.renderer.setShowGutter(true); 308 | $scope.editor.setHighlightActiveLine(true); 309 | $scope.editor.session.setUseWrapMode(false); 310 | $scope.editor.setTheme('ace/theme/ambiance'); 311 | } else { 312 | $scope.editor.renderer.setShowGutter(false); 313 | $scope.editor.setHighlightActiveLine(false); 314 | $scope.editor.session.setUseWrapMode(true); 315 | $scope.editor.setTheme('ace/theme/clouds'); 316 | } 317 | setTimeout(function(){ 318 | $scope.editor.resize(); 319 | }); 320 | }; 321 | 322 | $scope.$watch('fullscreenEdit', fullscreenToggle); 323 | 324 | $scope.$watch('file.nameUser', function() { 325 | if (!$scope.file || $scope.file.name == $scope.file.nameUser) { 326 | return; 327 | } 328 | console.log('CHANGED'); 329 | }); 330 | 331 | $scope.downloadFile = function(event, file) { 332 | event.preventDefault(); 333 | event.stopPropagation(); 334 | var iframe = document.getElementById('downloader'); 335 | iframe.src = $scope.path() + '?__p=dl&_d=' + file.path + '/' + file.name; 336 | }; 337 | 338 | $scope.saveFile = function() { 339 | $scope.$apply(function() { 340 | var error = function() { 341 | $scope.dialog = {type: 'error', message: 'There was an error saving the file.'}; 342 | }; 343 | $http.post($scope.path() + '/', { 344 | '__p': 'sv', 345 | '_d': $scope.dirPath(), 346 | 'c': $scope.editor.getSession().getValue() 347 | }). 348 | success(function(data) { 349 | if (data.status) { 350 | $scope.dialog = false; 351 | } else { 352 | error(); 353 | } 354 | }). 355 | error(error); 356 | }); 357 | }; 358 | 359 | $scope.upload = function(event, scope) { 360 | var files = event.target.files || event.dataTransfer.files; 361 | 362 | for (var i = 0, f; f = files[i]; i++) { 363 | var xhr = new XMLHttpRequest(); 364 | var file = files[i]; 365 | 366 | if (xhr.upload && file.size <= 9000000000) { 367 | var fd = new FormData(); 368 | fd.append(file.name, file); 369 | 370 | scope.$apply(function() { 371 | 372 | var upload = { 373 | name: file.name, 374 | path: $location.path(), 375 | size: file.size, 376 | uploaded: 0, 377 | progress: 0, 378 | status: 'uploading' 379 | }; 380 | scope.formatSize(upload); 381 | scope.uploads.push(upload); 382 | 383 | 384 | xhr.upload.addEventListener('progress', function(e) { 385 | scope.$apply(function() { 386 | upload.uploaded = e.loaded; 387 | upload.progress = parseInt(e.loaded / e.total * 100); 388 | }); 389 | }, false); 390 | 391 | xhr.onload = function() { 392 | var status = this.status; 393 | scope.$apply(function() { 394 | upload.status = (status == 200 ? 'success' : 'error'); 395 | scope.loadFiles(); 396 | }); 397 | setTimeout(function() { 398 | scope.$apply(function() { 399 | scope.uploads.splice(scope.uploads.indexOf(upload), 1); 400 | }); 401 | },5000); 402 | }; 403 | 404 | xhr.open('POST', scope.path() + '?__p=ul&_d=' + scope.dirPath(), true); 405 | xhr.setRequestHeader('X-File-Name', file.name); 406 | xhr.send(fd); 407 | }); 408 | } 409 | } 410 | }; 411 | }) 412 | .directive('ngEnter', function() { 413 | return function(scope, element, attrs) { 414 | if (attrs.ngEnter) { 415 | element.bind('keydown keypress', function(event) { 416 | if (event.which === 13) { 417 | event.preventDefault(); 418 | scope.$eval(attrs.ngEnter); 419 | } 420 | }); 421 | } 422 | }; 423 | }) 424 | .directive('ngDropUpload', function () { 425 | return function (scope, element) { 426 | 427 | var dragEnd = function() { 428 | scope.$apply(function() { 429 | scope.dialog = false; 430 | }); 431 | }; 432 | 433 | var autoEndClean; 434 | 435 | angular.element(document).bind('dragover', function(event) { 436 | clearTimeout(autoEndClean); 437 | for (var x in event.dataTransfer.files) { 438 | console.log(event.dataTransfer.files[x]); 439 | } 440 | event.preventDefault(); 441 | scope.$apply(function() { 442 | scope.dialog = { 443 | type: 'dropupload' 444 | } 445 | }); 446 | autoEndClean = setTimeout(dragEnd,1000); 447 | }); 448 | 449 | element 450 | .bind('drop', function(event) { 451 | event.preventDefault(); 452 | scope.upload(event, scope); 453 | }); 454 | } 455 | }) 456 | .directive('ngUploader', function($location) { 457 | return function(scope, element) { 458 | element.bind('change', function(event) { 459 | scope.upload(event, scope); 460 | }); 461 | }; 462 | }) 463 | .directive('ngUpload', function() { 464 | return function(scope, element) { 465 | element.bind('click', function(event) { 466 | document.querySelector('.upload').click(); 467 | }); 468 | }; 469 | }) 470 | .directive('ngMakeDir', function($http, $location) { 471 | return function(scope, element) { 472 | element.bind('click', function(event) { 473 | scope.$apply(function() { 474 | scope.dialog = { 475 | type: 'makeDir', 476 | path: scope.file, 477 | file: '', 478 | no: function() { 479 | scope.dialog = false; 480 | }, 481 | yes: function() { 482 | if (!scope.dialog.file) { 483 | return; 484 | } 485 | var error = function() { 486 | scope.dialog = {type: 'error', message: 'There was an error creating the folder.'}; 487 | }; 488 | $http({method: 'GET', url: scope.path() + '?__p=mk&_d=' + scope.dirPath() + '&_n=' + scope.dialog.file}). 489 | success(function(data) { 490 | if (data.status) { 491 | scope.dialog = false; 492 | scope.loadFiles(); 493 | } else { 494 | error(); 495 | } 496 | }). 497 | error(error); 498 | } 499 | }; 500 | }); 501 | }); 502 | }; 503 | }) 504 | .directive('ngDelete', function($http, $location) { 505 | return function(scope, element) { 506 | element.bind('click', function(event) { 507 | scope.$apply(function() { 508 | scope.dialog = { 509 | type: 'confirmDelete', 510 | file: scope.file, 511 | no: function() { 512 | scope.dialog = false; 513 | }, 514 | yes: function() { 515 | var error = function() { 516 | scope.dialog = {type: 'error', message: 'There was an error deleting the ' + (scope.type == 'dir' ? 'folder' : 'file') + '.'}; 517 | }; 518 | $http({method: 'GET', url: scope.path() + '?__p=rm&_d=' + scope.dirPath()}). 519 | success(function(data) { 520 | if (data.status) { 521 | scope.dialog = false; 522 | $location.path(scope.script + scope.file.path); 523 | } else { 524 | error(); 525 | } 526 | }). 527 | error(error); 528 | } 529 | }; 530 | }); 531 | }); 532 | }; 533 | }) 534 | .directive('ngSave', function($http, $location) { 535 | return function(scope, element) { 536 | element.bind('click', function(event) { 537 | scope.saveFile(); 538 | }); 539 | }; 540 | }) 541 | .directive('ngFullscreenEditor', function($http, $location) { 542 | return function(scope, element) { 543 | element.bind('click', function(event) { 544 | scope.$apply(function() { 545 | scope.fullscreenEdit = true; 546 | }); 547 | }); 548 | }; 549 | }) 550 | .directive('ngModal', function() { 551 | return function(scope, element) { 552 | element.bind('click', function(event) { 553 | if (event.target == element[0]) { 554 | scope.$apply(function() { 555 | scope.dialog = false; 556 | }); 557 | } 558 | }); 559 | }; 560 | }) 561 | .directive('ngBody', function() { 562 | return function(scope, element) { 563 | element.bind('keydown keypress', function(event) { 564 | if (event.which == 27) { 565 | scope.$apply(function() { 566 | scope.dialog = false; 567 | scope.fullscreenEdit = false; 568 | }); 569 | } 570 | }); 571 | }; 572 | }) 573 | .directive('ngAutofocus', function() { 574 | return function(scope, element, attrs) { 575 | if (attrs.ngAutofocus) { 576 | scope.$watch(attrs.ngAutofocus, function(value) { 577 | setTimeout(function() { 578 | element[0].focus(); 579 | },10); 580 | }); 581 | } 582 | }; 583 | }) 584 | .directive('ngFileName', function($http) { 585 | return function(scope, element) { 586 | var save = function() { 587 | var error = function() { 588 | scope.dialog = {type: 'error', message: 'There was an error renaming the file.'}; 589 | }; 590 | $http({method: 'GET', url: scope.path() + '?__p=rn&_d=' + scope.dirPath() + '&_n=' + scope.file.name}). 591 | success(function(data) { 592 | console.log(data); 593 | if (data.status) { 594 | 595 | } else { 596 | error(); 597 | } 598 | }). 599 | error(error); 600 | }; 601 | element 602 | .bind('change', function(event) { 603 | clearTimeout(scope.nameChange); 604 | scope.nameChange = setTimeout(save, 1000); 605 | }) 606 | .bind('blur', function(event) { 607 | save(); 608 | }); 609 | }; 610 | }); 611 | 612 | Object.size = function(obj) { 613 | var size = 0, key; 614 | for (key in obj) { 615 | if (obj.hasOwnProperty(key)) size++; 616 | } 617 | return size; 618 | }; 619 | 620 | -------------------------------------------------------------------------------- /public/cheryl.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cheryl 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 |

Filter by date

21 |
    22 |
  • {{val}}({{dates[key] || '0'}})
  • 23 |
24 |

25 |

Filter by type

26 |
    27 |
  • {{key}}({{val}})
  • 28 |
29 |

30 |

Sort by

31 |
    32 |
  • Name
  • 33 |
  • Date
  • 34 |
  • Type
  • 35 |
36 | 37 |
38 |
39 |

File info

40 |
    41 |
  • Created: {{file.ctime*1000 | date:'medium'}}
  • 42 |
  • Modified: {{file.mtime*1000 | date:'medium'}}
  • 43 |
  • Size: {{file.sizeReadable}}
  • 44 |
  • {{key}}: {{val}}
  • 45 |
46 |
47 |
48 | 49 |
50 | 51 | 52 | 56 |
57 |
58 |
59 |

60 |    61 | {{file.name || 'Home'}} 62 |

63 | {{file.path ? 'Home' + (file.path == '/' ? '' : file.path) : ''}} 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | 94 | 95 | 119 |
120 |
121 |
122 |
123 |
124 | 127 |
128 |
129 |
130 |
131 |
Welcome!
132 | 143 |
144 | 145 | 148 |
149 |
150 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacedevin/cheryl/b30c2134b8a0b6537274e81b45eb21214a9ed6af/public/icon.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | -1) { 23 | error_reporting(E_ALL ^ (E_NOTICE | E_STRICT)); 24 | ini_set('display_errors', true); 25 | } 26 | 27 | // set a tipsy database url if we have it defined 28 | if (getenv('DATABASE_URL')) { 29 | Tipsy::config(['db' => ['url' => getenv('DATABASE_URL')]]); 30 | } 31 | 32 | // include our config file 33 | Tipsy::app()->config('../config/config.yml'); 34 | 35 | // give Cheryl our config. this will merge with the default config 36 | // you can generate password hashes using password_hash('password', PASSWORD_BCRYPT) 37 | Cheryl::init([]); 38 | 39 | // do anything else you need before you start the request routing 40 | Cheryl::start(); 41 | -------------------------------------------------------------------------------- /public/wink.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Cheryl 4.0 Beta 2 | --- 3 | 4 | Cheryl is a web based file manager for the modern web. Built on top of PHP5 and AngularJS. 5 | 6 | 7 | #### Deploying on Heroku 8 | 9 | Deploying on Heroku will use Nginx and Postgres. 10 | 11 | [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 12 | 13 | --- 14 | 15 | 16 | #### Manual Install 17 | 18 | 1. Install [Composer](https://getcomposer.org/) 19 | 1. Run `composer install` from within your Cheryl directory 20 | 1. Edit any settings in **config/config.yml** 21 | 1. Ensure your **files** directory is chmod to 0777 if using localy stored files 22 | 1. Point your web root to the **public** directory 23 | 1. Login with your user/pass. Default is u: **admin**, p: **password**. 24 | -------------------------------------------------------------------------------- /src/Cheryl.php: -------------------------------------------------------------------------------- 1 | array( 23 | array( 24 | 'username' => 'admin', 25 | 'password' => '', 26 | 'permissions' => 'all' // if set to all, all permissions are enabled. even new features addedd in the future 27 | ) 28 | ), 29 | 'authentication' => 'simple', // simple: users are stored in the users array. mysql: uses a mysqli interface. pdo: uses pdo interface. see examples, 30 | 'root' => 'files', // the folder you want users to browse 31 | 'includes' => 'Cheryl', // path to look for additional libraries. leave blank if you dont know 32 | 'readonly' => false, // if true, disables all write features, and doesnt require authentication 33 | 'features' => array( 34 | 'snooping' => false, // if true, a user can browse filters behind the root directory, posibly exposing secure files. not reccomended 35 | 'recursiveBrowsing' => true, // if true, allows a simplified view that shows all files recursivly in a directory. with lots of files this can slow it down 36 | ), 37 | // files to hide from view 38 | 'hiddenFiles' => array( 39 | '.DS_Store', 40 | 'desktop.ini', 41 | '.git', 42 | '.svn', 43 | '.hg', 44 | '.thumb' 45 | ), 46 | 'libraries' => array( 47 | 'type' => 'remote' 48 | ), 49 | 'recursiveDelete' => true // if true, will allow deleting of unempty folders 50 | ); 51 | 52 | public $features = array( 53 | 'rewrite' => false, 54 | 'userewrite' => null, 55 | 'json' => false, 56 | 'gd' => false, 57 | 'exif' => false, 58 | 'imlib' => false, 59 | 'imcli' => false 60 | ); 61 | 62 | public $authed = false; 63 | 64 | 65 | public static function init($config = null) { 66 | if (!self::$_cheryl) { 67 | new Cheryl($config); 68 | } 69 | return self::$_cheryl; 70 | } 71 | 72 | public function __construct($config = null) { 73 | if (!self::$_cheryl) { 74 | self::$_cheryl = $this; 75 | } 76 | 77 | if (is_object($config)) { 78 | $config = (array)$config; 79 | } elseif(is_array($config)) { 80 | $config = $config; 81 | } else { 82 | $config = []; 83 | } 84 | 85 | $this->_tipsy = \Tipsy\Tipsy::app(); 86 | 87 | $this->config = array_merge($this->defaultConfig, $this->tipsy()->config()['cheryl']); 88 | $this->config = array_merge($this->config, $config); 89 | 90 | $this->_digestRequest(); 91 | $this->_setup(); 92 | $this->_authenticate(); 93 | } 94 | 95 | // method to grab object from static calls 96 | public static function me() { 97 | return self::$_cheryl; 98 | } 99 | 100 | public static function start() { 101 | self::me()->_request(); 102 | } 103 | 104 | public function _request() { 105 | $this->tipsy()->request()->path($this->tipsy()->request()->request()['__p']); 106 | $self = $this; 107 | 108 | $this->tipsy() 109 | ->get('logout', function() use ($self) { 110 | $self->_logout(); 111 | echo json_encode([ 112 | 'status' => true, 113 | 'message' => 'logged out' 114 | ]); 115 | }) 116 | ->post('login', function() use ($self) { 117 | $res = $self->_login(); 118 | if ($res) { 119 | echo json_encode([ 120 | 'status' => true, 121 | 'message' => 'logged in' 122 | ]); 123 | } else { 124 | echo json_encode([ 125 | 'status' => false, 126 | 'message' => 'failed to log in' 127 | ]); 128 | } 129 | }) 130 | ->get('config', function() use ($self) { 131 | $self->_getConfig(); 132 | }) 133 | ->get('ls', function() use ($self) { 134 | $self->_requestList(); 135 | }) 136 | ->get('dl', function() use ($self) { 137 | $self->_getFile(true); 138 | }) 139 | ->post('ul', function() use ($self) { 140 | $self->_takeFile(); 141 | }) 142 | ->get('vw', function() use ($self) { 143 | $self->_getFile(false); 144 | }) 145 | ->get('rm', function() use ($self) { 146 | $self->_deleteFile(); 147 | }) 148 | ->get('rn', function() use ($self) { 149 | $self->_renameFile(); 150 | }) 151 | ->get('mk', function() use ($self) { 152 | $self->_makeFile(); 153 | }) 154 | ->get('sv', function() use ($self) { 155 | $self->_saveFile(); 156 | }) 157 | ->otherwise(function($View) { 158 | $View->display('cheryl', ['path' => str_replace('index.php','', $_SERVER['PHP_SELF'])]); 159 | }); 160 | 161 | $this->tipsy()->run(); 162 | } 163 | 164 | public function _setup() { 165 | 166 | $this->features = (object)$this->features; 167 | 168 | if (file_exists($this->config['includes'])) { 169 | // use include root at script level 170 | $this->config['includes'] = realpath($this->config['includes']).DIRECTORY_SEPARATOR; 171 | 172 | } elseif (file_exists(dirname(__FILE__).DIRECTORY_SEPARATOR.$this->config['includes'])) { 173 | // use include root at lib level 174 | $this->config['includes'] = dirname(__FILE__).DIRECTORY_SEPARATOR.$this->config['includes'].DIRECTORY_SEPARATOR; 175 | 176 | } else { 177 | // use current path 178 | $this->config['includes'] = realpath(__FILE__).DIRECTORY_SEPARATOR; 179 | } 180 | 181 | if (!$this->config['root']) { 182 | $this->config['root'] = dirname($_SERVER['SCRIPT_FILENAME']).DIRECTORY_SEPARATOR; 183 | } else { 184 | $this->config['root'] = dirname($_SERVER['SCRIPT_FILENAME']).DIRECTORY_SEPARATOR.$this->config['root'].DIRECTORY_SEPARATOR; 185 | 186 | if (!file_exists($this->config['root'])) { 187 | @mkdir($this->config['root']); 188 | @chmod($this->config['root'], 0777); 189 | } 190 | } 191 | 192 | if ((function_exists('apache_get_modules') && in_array('mod_rewrite', apache_get_modules())) || getenv('HTTP_MOD_REWRITE') == 'On') { 193 | $this->features->rewrite = true; 194 | } 195 | 196 | if (function_exists('json_decode')) { 197 | $this->features->json = true; 198 | } 199 | 200 | if (function_exists('exif_read_data')) { 201 | $this->features->exif = true; 202 | } 203 | 204 | if (function_exists('getimagesize')) { 205 | $this->features->gd = true; 206 | } 207 | 208 | if (function_exists('Imagick::identifyImage')) { 209 | $this->features->imlib = true; 210 | } 211 | 212 | if (!$this->features->imlib) { 213 | $o = shell_exec('identify -version 2>&1'); 214 | if (!strpos($o, 'not found')) { 215 | $this->features->imcli = 'identify'; 216 | } elseif (file_exists('/usr/local/bin/identify')) { 217 | $this->features->imcli = '/usr/local/bin/identify'; 218 | } elseif(file_exists('/usr/bin/identify')) { 219 | $this->features->imcli = '/usr/bin/identify'; 220 | } elseif(file_exists('/opt/local/bin/identify')) { 221 | $this->features->imcli = '/opt/local/bin/identify'; 222 | } elseif(file_exists('/bin/identify')) { 223 | $this->features->imcli = '/bin/identify'; 224 | } elseif(file_exists('/usr/bin/identify')) { 225 | $this->features->imcli = '/usr/bin/identify'; 226 | } 227 | } 228 | 229 | $stat = intval(trim(shell_exec('stat -f %B '.escapeshellarg(__FILE__)))); 230 | if ($stat && intval(filemtime(__FILE__)) != $stat) { 231 | $this->features->ctime = true; 232 | } 233 | } 234 | 235 | public function _authenticate() { 236 | if (php_sapi_name() == 'cli') { 237 | return $this->authed = true; 238 | } 239 | 240 | if (!User::users()) { 241 | // allow anonymouse access. ur crazy! 242 | return $this->authed = true; 243 | } 244 | 245 | session_start(); 246 | 247 | if ($_SESSION['cheryl-authed']) { 248 | $this->user = new User($_SESSION['cheryl-username']); 249 | return $this->authed = true; 250 | } 251 | } 252 | 253 | public function _login() { 254 | $user = User::login( 255 | Cheryl::me()->tipsy()->request()->request()['__username'], 256 | Cheryl::me()->tipsy()->request()->request()['__password'] 257 | ); 258 | if ($user) { 259 | $this->user = $user; 260 | $this->authed = $_SESSION['cheryl-authed'] = true; 261 | $_SESSION['cheryl-username'] = $this->user->username; 262 | return true; 263 | 264 | } else { 265 | return false; 266 | } 267 | } 268 | 269 | public function _logout() { 270 | @session_destroy(); 271 | @session_regenerate_id(); 272 | @session_start(); 273 | } 274 | 275 | public function _digestRequest() { 276 | /* 277 | if ($this->request['__p']) { 278 | // we have a page param result 279 | $url = explode('/',$this->request['__p']); 280 | $this->features->userewrite = false; 281 | 282 | } else { 283 | $url = false; 284 | } 285 | */ 286 | 287 | $this->request = $this->tipsy()->request()->request(); 288 | 289 | // sanatize file/directory requests 290 | if ($this->request['_d']) { 291 | $this->request['_d'] = str_replace('/',DIRECTORY_SEPARATOR, $this->request['_d']); 292 | if ($this->config['features']['snooping']) { 293 | // just allow them to enter any old damn thing 294 | $this->requestDir = $this->config['root'].$this->request['_d']; 295 | } else { 296 | $this->requestDir = preg_replace('/\.\.\/|\.\//i','',$this->request['_d']); 297 | //$this->requestDir = preg_replace('@^'.DIRECTORY_SEPARATOR.basename(__FILE__).'@','',$this->requestDir); 298 | $this->requestDir = $this->config['root'].$this->requestDir; 299 | } 300 | 301 | if (file_exists($this->requestDir)) { 302 | $this->requestDir = dirname($this->requestDir).DIRECTORY_SEPARATOR.basename($this->requestDir); 303 | } else { 304 | $this->requestDir = null; 305 | } 306 | } 307 | 308 | // sanatize filename 309 | if ($this->request['_n']) { 310 | $this->requestName = preg_replace('@'.DIRECTORY_SEPARATOR.'@','',$this->request['_n']); 311 | } 312 | } 313 | 314 | public function _getFiles($dir, $filters = array()) { 315 | 316 | if ($filters['recursive']) { 317 | $iter = new \RecursiveDirectoryIterator($dir); 318 | $iterator = new \RecursiveIteratorIterator( 319 | $iter, 320 | \RecursiveIteratorIterator::SELF_FIRST, 321 | \RecursiveIteratorIterator::CATCH_GET_CHILD 322 | ); 323 | 324 | $filtered = new CherylFilterIterator($iterator); 325 | 326 | $paths = array($dir); 327 | foreach ($filtered as $path => $file) { 328 | if ($file->getFilename() == '.' || $file->getFilename() == '..') { 329 | continue; 330 | } 331 | if ($file->isDir()) { 332 | $dirs[] = $this->_getFileInfo($file); 333 | } elseif (!$file->isDir()) { 334 | $files[] = $this->_getFileInfo($file); 335 | } 336 | } 337 | 338 | } else { 339 | $iter = new CherylDirectoryIterator($dir); 340 | $filter = new CherylFilterIterator($iter); 341 | $iterator = new \IteratorIterator($filter); 342 | 343 | $paths = array($dir); 344 | foreach ($iterator as $path => $file) { 345 | if ($file->isDot()) { 346 | continue; 347 | } 348 | if ($file->isDir()) { 349 | $dirs[] = $this->_getFileInfo($file); 350 | } elseif (!$file->isDir()) { 351 | $files[] = $this->_getFileInfo($file); 352 | } 353 | } 354 | } 355 | 356 | return array('dirs' => $dirs, 'files' => $files); 357 | } 358 | 359 | public function _cTime($file) { 360 | return (int)trim(shell_exec('stat -f %B '.escapeshellarg($file->getPathname()))); 361 | } 362 | 363 | // do our own type detection 364 | public function _type($file, $extended = false) { 365 | 366 | $mime = mime_content_type($file->getPathname()); 367 | 368 | $mimes = explode('/',$mime); 369 | $type = strtolower($mimes[0]); 370 | $ext = $file->getExtension(); 371 | 372 | if ($ext == 'pdf') { 373 | $type = 'image'; 374 | } 375 | 376 | $ret = array( 377 | 'type' => $type, 378 | 'mime' => $mime 379 | ); 380 | 381 | if (!$extended) { 382 | return $ret['type']; 383 | } else { 384 | return $ret; 385 | } 386 | } 387 | 388 | public function _getFileInfo($file, $extended = false) { 389 | $path = str_replace(realpath($this->config['root']),'',realpath($file->getPath())); 390 | 391 | if ($file->isDir()) { 392 | $info = array( 393 | 'path' => $path, 394 | 'name' => $file->getFilename(), 395 | 'writeable' => $file->isWritable(), 396 | 'type' => $this->_type($file, false) 397 | ); 398 | 399 | } elseif (!$file->isDir()) { 400 | $info = array( 401 | 'path' => $path, 402 | 'name' => $file->getFilename(), 403 | 'size' => $file->getSize(), 404 | 'mtime' => $file->getMTime(), 405 | 'ext' => $file->getExtension(), 406 | 'writeable' => $file->isWritable() 407 | ); 408 | 409 | if ($this->features->ctime) { 410 | $info['ctime'] = $this->_cTime($file); 411 | } 412 | 413 | if ($extended) { 414 | $type = $this->_type($file, true); 415 | 416 | $info['type'] = $type['type']; 417 | 418 | $info['meta'] = array( 419 | 'mime' => $type['mime'] 420 | ); 421 | 422 | if ($type['type'] == 'text') { 423 | $info['contents'] = file_get_contents($file->getPathname()); 424 | } 425 | 426 | if (strpos($mime, 'image') > -1) { 427 | if ($this->features->exif) { 428 | $exif = @exif_read_data($file->getPathname()); 429 | 430 | if ($exif) { 431 | $keys = array('Make','Model','ExposureTime','FNumber','ISOSpeedRatings','FocalLength','Flash'); 432 | foreach ($keys as $key) { 433 | if (!$exif[$key]) { 434 | continue; 435 | } 436 | $exifInfo[$key] = $exif[$key]; 437 | } 438 | if ($exif['DateTime']) { 439 | $d = new \DateTime($exif['DateTime']); 440 | $exifInfo['Created'] = $d->getTimestamp(); 441 | } elseif ($exif['FileDateTime']) { 442 | $exifInfo['Created'] = $exif['FileDateTime']; 443 | } 444 | if ($exif['COMPUTED']['CCDWidth']) { 445 | $exifInfo['CCDWidth'] = $exif['COMPUTED']['CCDWidth']; 446 | } 447 | if ($exif['COMPUTED']['ApertureFNumber']) { 448 | $exifInfo['ApertureFNumber'] = $exif['COMPUTED']['ApertureFNumber']; 449 | } 450 | if ($exif['COMPUTED']['Height']) { 451 | $height = $exif['COMPUTED']['Height']; 452 | } 453 | if ($exif['COMPUTED']['Width']) { 454 | $width = $exif['COMPUTED']['Width']; 455 | } 456 | } 457 | } 458 | $info['meta']['exif'] = $exifInfo; 459 | } 460 | if (!$height || !$width) { 461 | if ($this->features->gd) { 462 | $is = @getimagesize($file->getPathname()); 463 | if ($is[0]) { 464 | $width = $is[0]; 465 | $height = $is[1]; 466 | } 467 | } 468 | } 469 | if ($height && $width) { 470 | $info['meta']['height'] = $height; 471 | $info['meta']['width'] = $width; 472 | } 473 | } 474 | 475 | $info['perms'] = $file->getPerms(); 476 | 477 | } 478 | return $info; 479 | } 480 | 481 | public function _getFile($download = false) { 482 | if (!$this->authed && !$this->config['readonly']) { 483 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 484 | exit; 485 | } 486 | 487 | if (!$this->requestDir || !is_file($this->requestDir)) { 488 | header('Status: 404 Not Found'); 489 | header('HTTP/1.0 404 Not Found'); 490 | exit; 491 | } 492 | 493 | $file = new \SplFileObject($this->requestDir); 494 | 495 | // not really sure if this range shit works. stole it from an old script i wrote 496 | if (isset($_SERVER['HTTP_RANGE'])) { 497 | list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2); 498 | if ($size_unit == 'bytes') { 499 | list($range, $extra_ranges) = explode(',', $range_orig, 2); 500 | } else { 501 | $range = ''; 502 | } 503 | 504 | if ($range) { 505 | list ($seek_start, $seek_end) = explode('-', $range, 2); 506 | } 507 | 508 | $seek_end = (empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)),($size - 1)); 509 | $seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0); 510 | 511 | if ($seek_start > 0 || $seek_end < ($size - 1)) { 512 | header('HTTP/1.1 206 Partial Content'); 513 | } else { 514 | header('HTTP/1.1 200 OK'); 515 | } 516 | header('Accept-Ranges: bytes'); 517 | header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$size); 518 | $contentLength = ($seek_end - $seek_start + 1); 519 | 520 | } else { 521 | header('HTTP/1.1 200 OK'); 522 | header('Accept-Ranges: bytes'); 523 | $contentLength = $file->getSize(); 524 | } 525 | 526 | header('Pragma: public'); 527 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 528 | header('Date: '.date('r')); 529 | header('Last-Modified: '.date('r',$file->getMTime())); 530 | header('Content-Length: '.$contentLength); 531 | header('Content-Transfer-Encoding: binary'); 532 | 533 | if ($download) { 534 | header('Content-Disposition: attachment; filename="'.$file->getFilename().'"'); 535 | header('Content-Type: application/force-download'); 536 | } else { 537 | header('Content-Type: '. mime_content_type($file->getPathname())); 538 | } 539 | 540 | // i wrote this freading a really long time ago but it seems to be more robust than SPL. someone correct me if im wrong 541 | $fp = fopen($file->getPathname(), 'rb'); 542 | fseek($fp, $seek_start); 543 | while(!feof($fp)) { 544 | set_time_limit(0); 545 | print(fread($fp, 1024*8)); 546 | flush(); 547 | ob_flush(); 548 | } 549 | fclose($fp); 550 | exit; 551 | } 552 | 553 | public function _requestList() { 554 | if (!$this->authed && !$this->config['readonly']) { 555 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 556 | exit; 557 | } 558 | 559 | if (!$this->requestDir) { 560 | header('Status: 404 Not Found'); 561 | header('HTTP/1.0 404 Not Found'); 562 | exit; 563 | } 564 | 565 | if (is_file($this->requestDir)) { 566 | $file = new \SplFileObject($this->requestDir); 567 | $info = $this->_getFileInfo($file, true); 568 | echo json_encode(array('type' => 'file', 'file' => $info)); 569 | 570 | } else { 571 | 572 | if (realpath($this->requestDir) == realpath($this->config['root'])) { 573 | $path = ''; 574 | $name = ''; 575 | } else { 576 | $dir = pathinfo($this->requestDir); 577 | $path = str_replace(realpath($this->config['root']),'',realpath($dir['dirname'])); 578 | $name = basename($this->requestDir); 579 | $path = $path{0} == '/' ? $path : '/'.$path; 580 | } 581 | $info = array( 582 | 'path' => $path, 583 | 'writeable' => is_writable($this->requestDir), 584 | 'name' => $name 585 | ); 586 | 587 | $files = $this->_getFiles($this->requestDir, array( 588 | 'recursive' => $this->request['filters']['recursive'] 589 | )); 590 | echo json_encode(array('type' => 'dir', 'list' => $files, 'file' => $info)); 591 | } 592 | } 593 | 594 | public function _takeFile() { 595 | if (!$this->authed) { 596 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 597 | exit; 598 | } 599 | if ($this->config['readonly'] || !$this->user->permission('upload', $this->requestDir)) { 600 | echo json_encode(array('status' => false, 'message' => 'no permission')); 601 | exit; 602 | } 603 | 604 | foreach ($_FILES as $file) { 605 | move_uploaded_file($file['tmp_name'],$this->requestDir.DIRECTORY_SEPARATOR.$file['name']); 606 | } 607 | 608 | echo json_encode(array('status' => true)); 609 | } 610 | 611 | public function _deleteFile() { 612 | if (!$this->authed) { 613 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 614 | exit; 615 | } 616 | if ($this->config['readonly'] || !$this->user->permission('delete', $this->requestDir)) { 617 | echo json_encode(array('status' => false, 'message' => 'no permission')); 618 | exit; 619 | } 620 | 621 | $status = false; 622 | 623 | if (is_dir($this->requestDir)) { 624 | if ($this->config['recursiveDelete']) { 625 | foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->requestDir), \RecursiveIteratorIterator::CHILD_FIRST) as $path) { 626 | if ($path->getFilename() == '.' || $path->getFilename() == '..') { 627 | continue; 628 | } 629 | $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname()); 630 | } 631 | } 632 | 633 | if (rmdir($this->requestDir)) { 634 | $status = true; 635 | } 636 | 637 | } else { 638 | if (unlink($this->requestDir)) { 639 | $status = true; 640 | } 641 | } 642 | 643 | echo json_encode(array('status' => $status)); 644 | } 645 | 646 | public function _renameFile() { 647 | if (!$this->authed) { 648 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 649 | exit; 650 | } 651 | if ($this->config['readonly'] || !$this->user->permission('rename', $this->requestDir)) { 652 | echo json_encode(array('status' => false, 'message' => 'no permission')); 653 | exit; 654 | } 655 | 656 | if (@rename($this->requestDir, dirname($this->requestDir).DIRECTORY_SEPARATOR.$this->requestName)) { 657 | $status = true; 658 | } else { 659 | $status = false; 660 | } 661 | 662 | echo json_encode(array('status' => $status, 'name' => $this->requestName)); 663 | } 664 | 665 | public function _makeFile() { 666 | if (!$this->authed) { 667 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 668 | exit; 669 | } 670 | if ($this->config['readonly'] || !$this->user->permission('create', $this->requestDir)) { 671 | echo json_encode(array('status' => false, 'message' => 'no permission')); 672 | exit; 673 | } 674 | 675 | if (@mkdir($this->requestDir.DIRECTORY_SEPARATOR.$this->requestName,0777)) { 676 | $status = true; 677 | } else { 678 | $status = false; 679 | } 680 | 681 | echo json_encode(array('status' => $status, 'name' => $this->requestName)); 682 | } 683 | 684 | public function _saveFile() { 685 | if (!$this->authed) { 686 | echo json_encode(array('status' => false, 'message' => 'not authenticated')); 687 | exit; 688 | } 689 | if ($this->config['readonly'] || !$this->user->permission('save', $this->requestDir)) { 690 | echo json_encode(array('status' => false, 'message' => 'no permission')); 691 | exit; 692 | } 693 | 694 | if (@file_put_contents($this->requestDir,$this->request['c'])) { 695 | $status = true; 696 | } else { 697 | $status = false; 698 | } 699 | 700 | echo json_encode(array('status' => $status)); 701 | } 702 | 703 | public function _getConfig() { 704 | echo json_encode([ 705 | 'status' => true, 706 | 'authed' => $this->authed, 707 | 'user' => $this->user ? $this->user->exports() : '' 708 | ]); 709 | } 710 | 711 | public static function iteratorFilter($current) { 712 | return !in_array( 713 | $current->getFileName(), 714 | self::me()->config['hiddenFiles'], 715 | true 716 | ); 717 | } 718 | 719 | public function tipsy() { 720 | return $this->_tipsy; 721 | } 722 | } 723 | 724 | 725 | class CherylFilterIterator extends \FilterIterator { 726 | public function accept() { 727 | return Cheryl::iteratorFilter($this->current()); 728 | } 729 | } 730 | 731 | class CherylDirectoryIterator extends \DirectoryIterator { 732 | public function getExtension() { 733 | if (method_exists(get_parent_class($this), 'getExtension')) { 734 | $ext = parent::getExtension(); 735 | } else { 736 | $ext = pathinfo($this->getPathName(), PATHINFO_EXTENSION); 737 | } 738 | return strtolower($ext); 739 | } 740 | } 741 | -------------------------------------------------------------------------------- /src/User.php: -------------------------------------------------------------------------------- 1 | config['users']; 9 | } 10 | 11 | public function permission($permission) { 12 | if ($this->permissions == 'all' || is_array($this->permissions) && $this->permissions[$perimssions] || is_object($this->permissions) && $this->permissions->{$perimssions}) { 13 | return true; 14 | } else { 15 | return false; 16 | } 17 | } 18 | 19 | public function exports() { 20 | return [ 21 | 'id' => $this->id_user, 22 | 'username' => $this->username, 23 | 'permissions' => $this->permissions 24 | ]; 25 | } 26 | 27 | public function loadUsername($username) { 28 | $user = $this->query('select * from `user` where username=? limit 1', [$username])->get(0); 29 | $this->load($user); 30 | } 31 | 32 | public function __construct($username = null) { 33 | $type = strtolower(Cheryl::me()->config['authentication']); 34 | 35 | switch ($type) { 36 | default: 37 | case 'simple': 38 | foreach (self::users() as $user) { 39 | if ($user['username'] == $username) { 40 | $u = $user; 41 | break; 42 | } 43 | } 44 | if ($u) { 45 | foreach($u as $key => $value) { 46 | $this->{$key} = $value; 47 | } 48 | } 49 | break; 50 | 51 | case 'pdo': 52 | $this->tipsy(\Tipsy\Tipsy::App()); 53 | $this->idVar('id_user')->table('user')->loadUsername($username); 54 | 55 | $perms = $this->db()->query('select * from `permission` WHERE id_user=? limit 1', [$this->id_user])->fetch(\PDO::FETCH_ASSOC); 56 | if ($perms) { 57 | $this->permissions = $perms['permission']; 58 | } 59 | break; 60 | } 61 | } 62 | 63 | public static function login($username, $password) { 64 | $username = trim($username); 65 | 66 | if (!$username) { 67 | return false; 68 | } 69 | 70 | $type = strtolower(Cheryl::me()->config['authentication']); 71 | 72 | // simple authentication. store users in an array 73 | if ($type == 'simple') { 74 | foreach (self::users() as $user) { 75 | if ($user['username'] == $username) { 76 | $u = $user; 77 | break; 78 | } 79 | } 80 | 81 | if ($u && ((!$u['password_hash'] && !$u['password']) || ($u['password_hash'] && password_verify($password, $u['password_hash']) || ($u['password'] && $password == $u['password'])))) { 82 | // successfuly send username and password 83 | return new User($u['username']); 84 | } 85 | 86 | return false; 87 | 88 | // use php data objects only if we have the libs 89 | } elseif ($type == 'pdo') { 90 | 91 | $u = new User($username); 92 | 93 | if ($u->id_user && password_verify($password, $u->password_hash)) { 94 | return $u; 95 | } 96 | 97 | // use php data objects only if we have the libs 98 | } elseif ($type == 'mysql' && function_exists('mysql_connect')) { 99 | // @todo #18 100 | } else { 101 | return false; 102 | } 103 | } 104 | } 105 | --------------------------------------------------------------------------------