├── scss-lint.yml ├── content └── index.php ├── install ├── config │ ├── database.php │ ├── error.php │ ├── app.php │ ├── session.php │ └── aliases.php ├── .htaccess ├── views │ ├── assets │ │ ├── img │ │ │ ├── logo.png │ │ │ ├── chosen-sprite.png │ │ │ └── chosen-sprite@2x.png │ │ └── js │ │ │ └── main.js │ ├── installed.php │ ├── partials │ │ ├── footer.php │ │ └── header.php │ ├── halt.php │ ├── complete.php │ ├── error │ │ ├── 404.php │ │ └── 500.php │ ├── account.php │ └── start.php ├── readme.md ├── storage │ ├── application.distro.php │ ├── session.distro.php │ ├── database.distro.php │ └── htaccess.distro ├── libraries │ ├── layout.php │ └── braces.php └── index.php ├── anchor ├── .htaccess ├── views │ ├── assets │ │ ├── img │ │ │ ├── cloud.png │ │ │ ├── cross.gif │ │ │ ├── icons.png │ │ │ ├── logo.png │ │ │ ├── piggy.gif │ │ │ ├── tick.gif │ │ │ ├── tick.png │ │ │ ├── favicon.png │ │ │ └── statuses.png │ │ ├── scss │ │ │ ├── components │ │ │ │ ├── _footer.scss │ │ │ │ ├── _paging.scss │ │ │ │ ├── editor │ │ │ │ │ ├── _half.scss │ │ │ │ │ ├── _redirect.scss │ │ │ │ │ ├── _preview.scss │ │ │ │ │ └── _files.scss │ │ │ │ ├── _fileUpload.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _notifications.scss │ │ │ │ ├── _editor.scss │ │ │ │ ├── _lists.scss │ │ │ │ └── _header.scss │ │ │ ├── _components.scss │ │ │ ├── _focusMode.scss │ │ │ ├── admin.scss │ │ │ ├── _layout.scss │ │ │ ├── _typography.scss │ │ │ ├── mixins │ │ │ │ ├── _brightness.scss │ │ │ │ ├── _contrast.scss │ │ │ │ └── _breakpoints.scss │ │ │ ├── _login.scss │ │ │ ├── _variables.scss │ │ │ └── _colors.scss │ │ └── js │ │ │ ├── page-name.js │ │ │ ├── text-resize.js │ │ │ ├── custom-fields.js │ │ │ ├── redirect.js │ │ │ ├── focus-mode.js │ │ │ ├── change-saver.js │ │ │ ├── sortable.js │ │ │ └── autosave.js │ ├── pages │ │ └── fields.php │ ├── extend │ │ ├── plugins │ │ │ └── index.php │ │ ├── pagetypes │ │ │ ├── index.php │ │ │ ├── add.php │ │ │ └── edit.php │ │ ├── variables │ │ │ ├── index.php │ │ │ ├── add.php │ │ │ └── edit.php │ │ ├── fields │ │ │ └── index.php │ │ └── index.php │ ├── partials │ │ ├── editor.php │ │ └── footer.php │ ├── profile.php │ ├── panel.php │ ├── users │ │ ├── reset.php │ │ ├── amnesia.php │ │ ├── index.php │ │ └── login.php │ ├── categories │ │ ├── index.php │ │ ├── add.php │ │ └── edit.php │ ├── menu │ │ └── index.php │ ├── error │ │ ├── 404.php │ │ └── 500.php │ ├── comments │ │ ├── index.php │ │ └── edit.php │ └── intro.php ├── language │ └── en_GB │ │ ├── menu.php │ │ ├── panel.php │ │ ├── categories.php │ │ ├── comments.php │ │ ├── pages.php │ │ ├── posts.php │ │ ├── metadata.php │ │ └── users.php ├── config │ ├── migrations.php │ ├── aliases.php │ └── error.php ├── migrations │ ├── 80_drop_sessions_ip.php │ ├── 81_drop_sessions_ua.php │ ├── 120_add_page_parent.php │ ├── 60_drop_posts_custom_fields.php │ ├── 130_pages_show_in_menu.php │ ├── 140_add_page_menu_order.php │ ├── 62_add_posts_category.php │ ├── 110_alter_session_date.php │ ├── 90_alter_users_password.php │ ├── 21_alter_comments_date.php │ ├── 210_fix_page_menu_order.php │ ├── 20_alter_comments_status.php │ ├── 61_alter_posts_created.php │ ├── 10_add_comment_notifications.php │ ├── 11_add_comment_moderation_keys.php │ ├── 70_create_categories_table.php │ ├── 71_insert_default_categories.php │ ├── 40_create_page_meta_table.php │ ├── 50_create_post_meta_table.php │ ├── 160_resize_post_html.php │ ├── 180_insert_meta_key.php │ ├── 220_alter_meta_field.php │ ├── 30_create_extend_table.php │ ├── 170_add_category_cust_field.php │ ├── 212_insert_default_dashboard_meta_key.php │ ├── 169_add_category_meta.php │ ├── 200_create_user_meta_table.php │ ├── 201_enable_pagetypes.php │ ├── 211_alter_post_page_content.php │ └── 213_add_updated_fields_to_tables.php ├── routes │ ├── panel.php │ ├── plugins.php │ └── menu.php ├── composer_check.php ├── libraries │ ├── response.php │ ├── hash.php │ ├── csrf.php │ ├── date.php │ ├── events.php │ ├── items.php │ ├── json.php │ ├── registry.php │ └── language.php ├── functions │ ├── metadata.php │ ├── config.php │ └── users.php ├── models │ ├── base.php │ ├── user.php │ └── category.php └── run.php ├── .github ├── anchor-bg.jpeg ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── test ├── unit │ ├── system.cli.spec.php │ ├── system.route.spec.php │ ├── system.database.spec.php │ ├── system.router.spec.php │ ├── README.md │ ├── system.database.query.spec.php │ ├── system.database.record.spec.php │ ├── system.database.builder.spec.php │ ├── system.database.connector.spec.php │ ├── system.database.connectors.mysql.spec.php │ ├── system.database.connectors.sqlite.spec.php │ ├── system.request.spec.php │ ├── system.config.spec.php │ ├── system.request.server.spec.php │ └── system.error.spec.php ├── integration │ └── README.md ├── teardown.js ├── travis-ci │ ├── release.sh │ ├── setup-apache.sh │ ├── apache_vhost │ └── setup-php-fpm.sh ├── peridot.php ├── setup.js ├── environment.js └── index.js ├── themes └── default │ ├── img │ ├── favicon.png │ ├── og_image.gif │ ├── search.png │ └── categories.png │ ├── about.txt │ ├── page.php │ ├── 404.php │ ├── css │ └── small.css │ ├── footer.php │ ├── search.php │ ├── posts.php │ ├── functions.php │ ├── js │ └── main.js │ └── article.php ├── .editorconfig ├── docker-compose.yml ├── jest.config.js ├── .gitignore ├── index.php ├── system ├── start.php ├── request.php ├── database │ └── connectors │ │ ├── sqlite.php │ │ └── mysql.php ├── boot.php ├── autoloader.php └── request │ └── server.php ├── composer.json ├── Dockerfile └── package.json /scss-lint.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /content/index.php: -------------------------------------------------------------------------------- 1 | 2 | Deny from all 3 | 4 | -------------------------------------------------------------------------------- /install/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | SetEnv HTTP_MOD_REWRITE On 3 | -------------------------------------------------------------------------------- /.github/anchor-bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anchorcms/anchor-cms/HEAD/.github/anchor-bg.jpeg -------------------------------------------------------------------------------- /test/unit/system.cli.spec.php: -------------------------------------------------------------------------------- 1 | 'Menu', 6 | 'edit_menu' => 'Edit menu' 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /install/config/error.php: -------------------------------------------------------------------------------- 1 | true, 5 | 'logger' => function ($exception) { 6 | } 7 | ]; 8 | -------------------------------------------------------------------------------- /install/views/assets/img/chosen-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anchorcms/anchor-cms/HEAD/install/views/assets/img/chosen-sprite.png -------------------------------------------------------------------------------- /test/unit/system.database.builder.spec.php: -------------------------------------------------------------------------------- 1 | MIGRATION_NUMBER 9 | ]; 10 | -------------------------------------------------------------------------------- /themes/default/about.txt: -------------------------------------------------------------------------------- 1 | Theme name: Default 2 | Description: This is the default, shiny theme for Anchor CMS. 3 | Author name: Visual Idiot 4 | Author site: http://visualidiot.com 5 | License: http://licence.visualidiot.com -------------------------------------------------------------------------------- /themes/default/page.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /anchor/views/pages/fields.php: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 |

6 | 7 | -------------------------------------------------------------------------------- /install/views/installed.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Anchor is already installed

6 | 7 | 8 |
9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /install/storage/application.distro.php: -------------------------------------------------------------------------------- 1 | '{{url}}', 5 | 'index' => '{{index}}', 6 | 'timezone' => '{{timezone}}', 7 | 'key' => '{{key}}', 8 | 'language' => '{{language}}', 9 | 'encoding' => 'UTF-8' 10 | ]; 11 | -------------------------------------------------------------------------------- /anchor/views/extend/plugins/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Plugins

5 |
6 | 7 |
8 |

9 | Soon. 10 |

11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /install/config/app.php: -------------------------------------------------------------------------------- 1 | dirname($_SERVER['SCRIPT_NAME']), 5 | 'index' => 'index.php?route=', 6 | 'timezone' => 'UTC', 7 | 'key' => hash('md5', 'Anchor Installer ' . VERSION), 8 | 'language' => 'en_GB', 9 | 'encoding' => 'UTF-8' 10 | ]; 11 | -------------------------------------------------------------------------------- /anchor/views/partials/editor.php: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_footer.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | 3 | .bottom { 4 | padding: 40px 0 30px; 5 | 6 | small { 7 | float: left; 8 | font-size: $font-size-small; 9 | } 10 | 11 | em { 12 | float: right; 13 | font-size: $font-size-small; 14 | font-style: normal; 15 | color: $color-gray-chateau; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [*.php] 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [Makefile] 15 | indent_style = tab 16 | 17 | [{Dockerfile,*.json,*.sh,*.yml,*.yaml}] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /anchor/views/profile.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |

6 | 7 | 8 |

9 |

10 |
11 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_paging.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | 3 | .paging { 4 | clear: both; 5 | text-align: center; 6 | padding-top: 25px; 7 | 8 | a, 9 | strong { 10 | display: inline-block; 11 | padding: 0.2em 1em; 12 | background: $color-white; 13 | border-radius: 5px; 14 | color: $color-slate-gray; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | A brief but detailed explanation of the issue or bug you are reporting. 4 | 5 | ### Expected Behaviour 6 | 7 | - A list of steps you expected to happen in order 8 | 9 | ### Actual Behaviour 10 | 11 | - A list of steps you actually encountered 12 | 13 | ### Context details (if applicable) 14 | 15 | - Anchor version: 16 | - Server setup: 17 | - URL 18 | -------------------------------------------------------------------------------- /install/config/session.php: -------------------------------------------------------------------------------- 1 | 'anchorcms-install', 5 | 'gc_probability' => '0', 6 | 'cookie_lifetime' => 86400, 7 | 'cookie_path' => '/', 8 | 'cookie_domain' => '', 9 | 'cookie_secure' => false, 10 | 'hash_function' => 'sha256', 11 | 'use_cookies' => true, 12 | 'use_only_cookies' => true, 13 | ]; 14 | -------------------------------------------------------------------------------- /install/storage/session.distro.php: -------------------------------------------------------------------------------- 1 | 'anchorcms', 5 | 'gc_probability' => '0', 6 | 'cookie_lifetime' => 86400, 7 | 'cookie_path' => '/', 8 | 'cookie_domain' => '', 9 | 'cookie_secure' => false, 10 | 'hash_function' => 'sha256', 11 | 'use_cookies' => true, 12 | 'use_only_cookies' => true, 13 | ]; 14 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/editor/_half.scss: -------------------------------------------------------------------------------- 1 | .half { 2 | float: left; 3 | width: 48.5%; 4 | margin-right: 3%; 5 | 6 | & + .half { 7 | margin-right: 0; 8 | } 9 | 10 | label { 11 | & + input, 12 | & + textarea, 13 | & + select { 14 | width: auto; 15 | min-width: 305px; 16 | } 17 | } 18 | 19 | em, 20 | legend { 21 | display: none; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/integration/README.md: -------------------------------------------------------------------------------- 1 | Integration tests (JavaScript) 2 | ============================== 3 | 4 | This directory contains the Integration tests using Puppeteer and Jest. This isn't about testing the 5 | JavaScript side of things, however, but rather the complete user interaction with AnchorCMS. 6 | Using Puppeteer, we can spin up a fully fledged headless version of Google Chrome that will carry out 7 | anything we command it to. 8 | -------------------------------------------------------------------------------- /anchor/views/assets/js/page-name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mirrors the page title into the page name field which is use in the menus 3 | */ 4 | $(function(input, output) { 5 | var input = $('input[name=title]'), output = $('input[name=name]'); 6 | var changed = false; 7 | 8 | output.bind('keyup', function() { 9 | changed = true; 10 | }); 11 | 12 | input.bind('keyup', function() { 13 | if( ! changed) output.val(input.val()); 14 | }); 15 | }); -------------------------------------------------------------------------------- /anchor/views/assets/scss/_components.scss: -------------------------------------------------------------------------------- 1 | @import "components/buttons"; 2 | @import "components/editor"; 3 | @import "components/fileUpload"; 4 | @import "components/footer"; 5 | @import "components/forms"; 6 | @import "components/header"; 7 | // @import "components/links"; 8 | @import "components/lists"; 9 | @import "components/notifications"; 10 | @import "components/paging"; 11 | @import "components/panel"; 12 | @import "components/sidebar"; 13 | -------------------------------------------------------------------------------- /anchor/migrations/80_drop_sessions_ip.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'ip')) { 11 | $sql = 'ALTER TABLE `' . $table . '` DROP `ip`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/81_drop_sessions_ua.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'ua')) { 11 | $sql = 'ALTER TABLE `' . $table . '` DROP `ua`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/language/en_GB/panel.php: -------------------------------------------------------------------------------- 1 | 'Administration Panel', 6 | 'title' => 'Welcome to your Anchor site', 7 | 'message' => 'Here you will find all of the tools you will need to produce content for your website, manage users, posts and pages. For more information on what you can do with Anchor, please see our documentation at http://anchorcms.com/docs', 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/editor/_redirect.scss: -------------------------------------------------------------------------------- 1 | @import "../../variables"; 2 | 3 | .redirect { 4 | height: 0; 5 | background: $color-alabaster; 6 | opacity: 0; 7 | transition: all 0.3s; 8 | 9 | &.show { 10 | top: 0; 11 | height: 37px; 12 | opacity: 0.99; 13 | } 14 | 15 | input { 16 | width: 100%; 17 | padding: 10px 0; 18 | color: $color-gull-gray; 19 | background: none; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /install/views/partials/footer.php: -------------------------------------------------------------------------------- 1 | 2 | You’re installing Anchor version . 3 | Need help? 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /anchor/migrations/120_add_page_parent.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'parent')) { 11 | $sql = 'ALTER TABLE `' . $table . '` ADD `parent` int(6) NOT NULL AFTER `id`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/views/panel.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 |

9 |
10 |

http://anchorcms.com/docs'); ?>

12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /anchor/migrations/60_drop_posts_custom_fields.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'custom_fields')) { 11 | $sql = 'ALTER TABLE `' . $table . '` DROP `custom_fields`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /themes/default/404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Page not found

5 | 6 |

Unfortunately, the page / could not be found. Your best bet is either to try the homepage, try searching, or go and cry in a corner (although I don’t recommend the latter).

7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /anchor/migrations/130_pages_show_in_menu.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'show_in_menu')) { 11 | $sql = 'ALTER TABLE `' . $table . '` ADD `show_in_menu` tinyint(1) NOT NULL'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/140_add_page_menu_order.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'menu_order')) { 11 | $sql = 'ALTER TABLE `' . $table . '` ADD `menu_order` int(4) NOT NULL DEFAULT 0'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/62_add_posts_category.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'category')) { 11 | $sql = 'ALTER TABLE `' . $table . '` ADD `category` int(6) NOT NULL AFTER `author`'; 12 | DB::query($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/110_alter_session_date.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'date')) { 11 | $sql = 'ALTER TABLE `' . $table . '` CHANGE `date` `expire` int(10) NOT NULL AFTER `id`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/routes/panel.php: -------------------------------------------------------------------------------- 1 | 'auth,csrf'], function () { 7 | 8 | // TODO: Unused page parameter, what for? 9 | Route::get('admin/panel', function ($page = 1) { 10 | $vars['token'] = Csrf::token(); 11 | 12 | return View::create('panel', $vars) 13 | ->partial('header', 'partials/header') 14 | ->partial('footer', 'partials/footer'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /anchor/views/partials/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /anchor/migrations/90_alter_users_password.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'password')) { 11 | $sql = 'ALTER TABLE `' . $table . '` CHANGE `password` `password` text NOT NULL AFTER `username`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/21_alter_comments_date.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = 'ALTER TABLE `' . $table . '` CHANGE `date` `date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/210_fix_page_menu_order.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'menu_order')) { 11 | $sql = 'ALTER TABLE `' . $table . '`;'; 12 | $sql .= 'ALTER COLUMN `menu_order` SET DEFAULT 0'; 13 | DB::ask($sql); 14 | } 15 | } 16 | 17 | public function down() 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /anchor/migrations/20_alter_comments_status.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = 'ALTER TABLE `' . $table . '` CHANGE `status` `status` enum(\'pending\',\'approved\',\'spam\') NOT NULL AFTER `post`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /anchor/migrations/61_alter_posts_created.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'created')) { 11 | $sql = 'ALTER TABLE `' . $table . '` CHANGE `created` `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `js`'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /install/storage/database.distro.php: -------------------------------------------------------------------------------- 1 | 'mysql', 5 | 'prefix' => '{{prefix}}', 6 | 'connections' => [ 7 | 'mysql' => [ 8 | 'driver' => '{{driver}}', 9 | 'hostname' => '{{hostname}}', 10 | 'port' => '{{port}}', 11 | 'username' => '{{username}}', 12 | 'password' => '{{password}}', 13 | 'database' => '{{database}}', 14 | 'charset' => '{{charset}}' 15 | ] 16 | ] 17 | ]; 18 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/_focusMode.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .top, .meta, .buttons { 4 | transition: opacity $transition-speed-slow; 5 | opacity: 1; 6 | } 7 | 8 | .header { 9 | transition: background $transition-speed-slow; 10 | } 11 | 12 | .focus { 13 | .top, .meta, .buttons { 14 | opacity: 0; 15 | pointer-events: none; 16 | } 17 | 18 | .meta { 19 | height: 0; 20 | overflow: hidden; 21 | } 22 | 23 | .header, 24 | .main textarea { 25 | background: $color-athens-gray; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /anchor/routes/plugins.php: -------------------------------------------------------------------------------- 1 | 'auth,csrf,install_exists'], function () { 7 | 8 | /** 9 | * List all plugins 10 | */ 11 | // TODO: Unused parameter $page? 12 | Route::get('admin/extend/plugins', function ($page = 1) { 13 | $vars['token'] = Csrf::token(); 14 | 15 | return View::create('extend/plugins/index', $vars) 16 | ->partial('header', 'partials/header') 17 | ->partial('footer', 'partials/footer'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/editor/_preview.scss: -------------------------------------------------------------------------------- 1 | @import "../../variables"; 2 | 3 | .prevue { 4 | position: absolute; 5 | left: 0; 6 | right: 0; 7 | top: -100px; 8 | min-height: 250px; 9 | padding: 40px 0; 10 | pointer-events: none; 11 | background: $color-athens-gray; 12 | z-index: 10; 13 | transition: top $transition-speed-base, opacity $transition-speed-slow; 14 | 15 | &.active { 16 | top: 164px; 17 | } 18 | 19 | & h2 { 20 | padding: 0 0 15px; 21 | font-size: 25px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /anchor/migrations/10_add_comment_notifications.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | if (! Query::table($table)->where('key', '=', 'comment_notifications')->count()) { 12 | Query::table($table)->insert(array( 13 | 'key' => 'comment_notifications', 14 | 'value' => 0 15 | )); 16 | } 17 | } 18 | } 19 | 20 | public function down() 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /anchor/migrations/11_add_comment_moderation_keys.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | if (! Query::table($table)->where('key', '=', 'comment_moderation_keys')->count()) { 12 | Query::table($table)->insert(array( 13 | 'key' => 'comment_moderation_keys', 14 | 'value' => '' 15 | )); 16 | } 17 | } 18 | } 19 | 20 | public function down() 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/teardown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | global window, 5 | document 6 | */ 7 | 8 | const chalk = require( 'chalk' ); 9 | const puppeteer = require( 'puppeteer' ); 10 | const rimraf = require( 'rimraf' ); 11 | const os = require( 'os' ); 12 | const path = require( 'path' ); 13 | const jestConfig = require( '../jest.config' ); 14 | 15 | const baseDirectory = path.join( os.tmpdir(), 'jest_puppeteer_global_setup' ); 16 | 17 | module.exports = async function () { 18 | jestConfig.globals.__DEBUG__ && console.log( chalk.green( 'Teardown Puppeteer' ) ); 19 | 20 | await global.__BROWSER__.close(); 21 | 22 | rimraf.sync( baseDirectory ); 23 | }; 24 | -------------------------------------------------------------------------------- /anchor/migrations/70_create_categories_table.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = 'CREATE TABLE IF NOT EXISTS `' . $table . '` ( 12 | `id` int(6) NOT NULL AUTO_INCREMENT, 13 | `title` varchar(150) NOT NULL, 14 | `slug` varchar(40) NOT NULL, 15 | `description` text NOT NULL, 16 | PRIMARY KEY (`id`) 17 | ) ENGINE=InnoDB'; 18 | 19 | DB::query($sql); 20 | } 21 | } 22 | 23 | public function down() 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /anchor/views/assets/js/text-resize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Textarea auto resize 3 | */ 4 | $(function() { 5 | var $text = $('textarea').first(); 6 | 7 | function resize(e) { 8 | var bodyScrollPos = window.pageYOffset; 9 | // $text.height('auto'); 10 | $text.height($text.prop('scrollHeight') + 'px'); 11 | window.scrollTo(0,bodyScrollPos); 12 | } 13 | 14 | /* 0-timeout to get the already changed text */ 15 | function delayedResize (e) { 16 | window.setTimeout(function(){ 17 | resize(e); 18 | }, 0); 19 | } 20 | 21 | $text.on('change', resize); 22 | $text.on('cut paste drop keydown', delayedResize); 23 | 24 | $text.focus(); 25 | $text.select(); 26 | resize(); 27 | }); 28 | -------------------------------------------------------------------------------- /test/travis-ci/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd $TRAVIS_BUILD_DIR 4 | 5 | # install optimized composer dependencies 6 | rm -rf ./vendor ./composer.lock 7 | composer install --no-dev --prefer-dist --optimize-autoloader --ignore-platform-reqs --no-interaction --no-suggest --no-progress 8 | 9 | # build static frontend assets 10 | npm run build 11 | 12 | mkdir ./release-build 13 | 14 | # copy all relevant files and directories to the build directory 15 | cp -R ./anchor ./content ./system ./system ./themes ./vendor ./index.php ./composer.json ./composer.lock ./readme.md ./license.md ./release-build/ 16 | 17 | # create an archive from it 18 | tar czf ./release.tar.gz -C release-build/ * 19 | -------------------------------------------------------------------------------- /anchor/migrations/71_insert_default_categories.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | if (! Query::table($table)->count()) { 12 | Query::table($table)->insert(array( 13 | 'title' => 'Uncategorised', 14 | 'slug' => 'uncategorised', 15 | 'description' => 'Ain\'t no category here.' 16 | )); 17 | } 18 | } 19 | } 20 | 21 | public function down() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /install/views/halt.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Woops!

6 | 7 | 1): ?> 8 | 13 | 14 |

15 | 16 | 17 |

18 | Let's try that again. 19 |

20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | 5 | # Anchor Apache Stack 6 | anchor: 7 | build: . 8 | container_name: anchor_cms 9 | ports: 10 | - "80:80" 11 | volumes: 12 | - .:/var/www/html 13 | environment: 14 | COMPOSE_PROJECT_NAME: "anchor_cms" 15 | 16 | # Database 17 | mysql: 18 | image: mariadb 19 | container_name: anchor_cms_mysql 20 | ports: 21 | - 3306:3306 22 | environment: 23 | MYSQL_ROOT_PASSWORD: "password" 24 | MYSQL_DATABASE: "anchor_cms" 25 | MYSQL_USER: "username" 26 | MYSQL_PASSWORD: "password" 27 | COMPOSE_PROJECT_NAME: "anchor_cms" 28 | logging: 29 | driver: none 30 | -------------------------------------------------------------------------------- /anchor/config/aliases.php: -------------------------------------------------------------------------------- 1 | 'System\\Arr', 5 | 'Autoloader' => 'System\\Autoloader', 6 | 'Config' => 'System\\Config', 7 | 'Cookie' => 'System\\Cookie', 8 | 'DB' => 'System\\Database', 9 | 'Errors' => 'System\\Error', 10 | 'Input' => 'System\\Input', 11 | 'Query' => 'System\\Database\\Query', 12 | 'Record' => 'System\\Database\\Record', 13 | 'Request' => 'System\\Request', 14 | 'Route' => 'System\\Route', 15 | 'Router' => 'System\\Router', 16 | 'Session' => 'System\\Session', 17 | 'Uri' => 'System\\Uri', 18 | 'View' => 'System\\View' 19 | ]; 20 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/admin.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Anchor CMS - admin interface 3 | * v2.0, by @anchorcms 4 | */ 5 | 6 | /** 7 | * Normalize.css 8 | * note: the ".css" at the end is missing on purpose - that way, Sass will inline the file 9 | * contents instead of outputting a normal CSS import statement. 10 | */ 11 | @import "../../../../node_modules/normalize.css/normalize"; 12 | 13 | /** 14 | * Typography 15 | */ 16 | @import "typography"; 17 | 18 | /** 19 | * Layout 20 | */ 21 | @import "layout"; 22 | 23 | /** 24 | * Page header 25 | */ 26 | @import "components"; 27 | 28 | /** 29 | * Focus mode 30 | */ 31 | @import "focusMode"; 32 | 33 | /** 34 | * Login 35 | */ 36 | @import "login"; 37 | -------------------------------------------------------------------------------- /anchor/config/error.php: -------------------------------------------------------------------------------- 1 | true, 5 | 'log' => function ($e) { 6 | /** @var \Exception $e */ 7 | 8 | $data = [ 9 | 'date' => date("Y-m-d H:i:s"), 10 | 'message' => $e->getMessage(), 11 | 'trace' => $e->getTrace(), 12 | 'line' => $e->getLine(), 13 | 'file' => $e->getFile() 14 | ]; 15 | 16 | file_put_contents(APP . 'errors.log', json_encode($data) . "\n", FILE_APPEND | LOCK_EX); 17 | 18 | echo '
';
19 |         echo $e->getMessage() . "\n";
20 |         echo 'The error has been logged in /anchor/errors.log';
21 |         echo '
'; 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /anchor/views/users/reset.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 | 7 |
8 |

9 | __('users.new_password'), 'id' => 'label-pass']); ?>

10 |

11 | 'submit']); ?> 12 |

13 |
14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /anchor/migrations/40_create_page_meta_table.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = "CREATE TABLE IF NOT EXISTS `' . $table . '` ( 12 | `id` int(6) NOT NULL AUTO_INCREMENT, 13 | `page` int(6) NOT NULL, 14 | `extend` int(6) NOT NULL, 15 | `data` text NOT NULL, 16 | PRIMARY KEY (`id`), 17 | KEY `page` (`page`), 18 | KEY `extend` (`extend`) 19 | ) ENGINE=InnoDB"; 20 | 21 | DB::ask($sql); 22 | } 23 | } 24 | 25 | public function down() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /anchor/migrations/50_create_post_meta_table.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = "CREATE TABLE IF NOT EXISTS `' . $table . '` ( 12 | `id` int(6) NOT NULL AUTO_INCREMENT, 13 | `post` int(6) NOT NULL, 14 | `extend` int(6) NOT NULL, 15 | `data` text NOT NULL, 16 | PRIMARY KEY (`id`), 17 | KEY `item` (`post`), 18 | KEY `extend` (`extend`) 19 | ) ENGINE=InnoDB"; 20 | 21 | DB::ask($sql); 22 | } 23 | } 24 | 25 | public function down() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /anchor/migrations/160_resize_post_html.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'html')) { 11 | $sql = 'ALTER TABLE `' . $table . '` MODIFY COLUMN `html` MEDIUMTEXT NOT NULL'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | $table = Base::table('posts'); 19 | 20 | if ($this->has_table_column($table, 'html')) { 21 | $sql = 'ALTER TABLE `' . $table . '` MODIFY COLUMN `html` TEXT NOT NULL'; 22 | DB::ask($sql); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /install/libraries/layout.php: -------------------------------------------------------------------------------- 1 | partial('header', 'partials/header', $vars) 23 | ->partial('footer', 'partials/footer', $vars); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/travis-ci/setup-apache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set up Apache for php-fpm 4 | # @see https://github.com/travis-ci/travis-ci.github.com/blob/master/docs/user/languages/php.md#apache--php 5 | 6 | sudo a2enmod rewrite actions fastcgi alias ssl 7 | 8 | # configure apache virtual hosts 9 | sudo cp -f test/travis-ci/apache_vhost /etc/apache2/sites-available/default 10 | sudo cp -f test/travis-ci/apache_vhost /etc/apache2/sites-available/000-default.conf 11 | sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/default 12 | sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/000-default.conf 13 | 14 | sudo a2ensite 000-default.conf 15 | sudo service apache2 restart 16 | -------------------------------------------------------------------------------- /install/config/aliases.php: -------------------------------------------------------------------------------- 1 | 'System\\Arr', 5 | 'Autoloader' => 'System\\Autoloader', 6 | 'Config' => 'System\\Config', 7 | 'Cookie' => 'System\\Cookie', 8 | 'DB' => 'System\\Database', 9 | 'Errors' => 'System\\Error', 10 | 'Input' => 'System\\Input', 11 | 'Query' => 'System\\Database\\Query', 12 | 'Record' => 'System\\Database\\Record', 13 | 'Request' => 'System\\Request', 14 | 'Response' => 'System\\Response', 15 | 'Route' => 'System\\Route', 16 | 'Router' => 'System\\Router', 17 | 'Session' => 'System\\Session', 18 | 'Uri' => 'System\\Uri', 19 | 'View' => 'System\\View' 20 | ]; 21 | -------------------------------------------------------------------------------- /anchor/migrations/180_insert_meta_key.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | if (! Query::table($table)->where('key', '=', 'show_all_posts')->count()) { 12 | Query::table($table)->insert(array( 13 | 'key' => 'show_all_posts', 14 | 'value' => 0 15 | )); 16 | } else { 17 | Query::table($table)->where('key', '=', 'show_all_posts')->update(array('value' => 0)); 18 | } 19 | } 20 | } 21 | 22 | public function down() 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /anchor/composer_check.php: -------------------------------------------------------------------------------- 1 | &1 so STDERR also goes to STDOUT. 10 | exec("$cmd 2>&1", $out, $completeGud); 11 | 12 | if ($completeGud !== 0) { 13 | die('We were unable to run composer our selves. Please run "composer install" from the command line to install Anchor. If you do not have composer installed please see https://getcomposer.org/
(Error #' . $completeGud . ') Here is the output of the command:
' . implode("
", 14 | $out) . '
'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /anchor/migrations/220_alter_meta_field.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 9 | $sql = "ALTER TABLE `" . $table . "` CHANGE `field` `field` enum('text','html','image','file','toggle') NOT NULL AFTER `type`"; 10 | DB::ask($sql); 11 | } 12 | } 13 | public function down() 14 | { 15 | $table = Base::table('extend'); 16 | if ($this->has_table($table)) { 17 | $sql = "ALTER TABLE `" . $table . "` CHANGE `field` `field` enum('text','html','image','file') NOT NULL AFTER `type`"; 18 | DB::ask($sql); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /anchor/migrations/30_create_extend_table.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = "CREATE TABLE IF NOT EXISTS `' . $table . '` ( 12 | `id` int(6) NOT NULL AUTO_INCREMENT, 13 | `type` enum('post','page') NOT NULL, 14 | `field` enum('text','html','image','file') NOT NULL, 15 | `key` varchar(160) NOT NULL, 16 | `label` varchar(160) NOT NULL, 17 | `attributes` text NOT NULL, 18 | PRIMARY KEY (`id`) 19 | ) ENGINE=InnoDB"; 20 | 21 | DB::ask($sql); 22 | } 23 | } 24 | 25 | public function down() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /anchor/migrations/170_add_category_cust_field.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'type')) { 11 | $sql = 'ALTER TABLE `' . $table . '` MODIFY COLUMN `type` enum("post", "page", "category") NOT NULL'; 12 | DB::ask($sql); 13 | } 14 | } 15 | 16 | public function down() 17 | { 18 | $table = Base::table('extend'); 19 | 20 | if ($this->has_table_column($table, 'type')) { 21 | $sql = 'ALTER TABLE `' . $table . '` MODIFY COLUMN `type` enum("post", "page") NOT NULL'; 22 | DB::ask($sql); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /anchor/migrations/212_insert_default_dashboard_meta_key.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 10 | if (! Query::table($table)->where('key', '=', 'dashboard_page')->count()) { 11 | Query::table($table)->insert(array( 12 | 'key' => 'dashboard_page', 13 | 'value' => 'panel' 14 | )); 15 | } else { 16 | Query::table($table)->where('key', '=', 'dashboard_page')->update(array('value' => 'panel')); 17 | } 18 | } 19 | } 20 | 21 | public function down() 22 | { 23 | } 24 | } -------------------------------------------------------------------------------- /anchor/libraries/response.php: -------------------------------------------------------------------------------- 1 | DB::profile()])->render(); 23 | 24 | $this->output = preg_replace('##', $profile . '', $this->output); 25 | } 26 | 27 | parent::send(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/_layout.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "mixins/breakpoints"; 3 | 4 | * { 5 | box-sizing: border-box; 6 | } 7 | 8 | html, 9 | body { 10 | width: 100%; 11 | 12 | @include breakpoint($breakpoint-small) { 13 | width: initial; 14 | } 15 | } 16 | 17 | .wrap { 18 | width: 90%; 19 | 20 | @include breakpoint($breakpoint-small) { 21 | width: initial; 22 | } 23 | } 24 | 25 | .wrap, 26 | .profile { 27 | width: 960px; 28 | margin: 0 auto; 29 | overflow: hidden; 30 | } 31 | 32 | fieldset, 33 | a, 34 | img { 35 | border: none; 36 | } 37 | 38 | .indent { 39 | padding-left: 40px; 40 | } 41 | 42 | p { 43 | margin: 0; 44 | } 45 | 46 | fieldset { 47 | margin: 0; 48 | padding: 0; 49 | } 50 | 51 | ul { 52 | margin: 0; 53 | padding: 0; 54 | } 55 | -------------------------------------------------------------------------------- /anchor/migrations/169_add_category_meta.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ( 12 | `id` int(6) NOT NULL AUTO_INCREMENT, 13 | `category` int(6) NOT NULL, 14 | `extend` int(6) NOT NULL, 15 | `data` text NOT NULL, 16 | PRIMARY KEY (`id`), 17 | KEY `item` (`category`), 18 | KEY `extend` (`extend`) 19 | ) ENGINE=InnoDB"; 20 | 21 | DB::ask($sql); 22 | } 23 | } 24 | 25 | public function down() 26 | { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /install/views/complete.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |

Install complete!

7 | 8 | 9 |

We could not write the htaccess file for you, copy 10 | the contents below and create a .htaccess in your Anchor root folder. 11 |

12 | 13 | 14 | 15 | 16 |
17 | Visit your admin panel 18 | Visit your new site 19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/_typography.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | * { 4 | -webkit-font-smoothing: antialiased; 5 | } 6 | 7 | body { 8 | font: #{$font-size-base}/#{$line-height-base} $font-family-base; 9 | color: $page-color; 10 | } 11 | 12 | b, 13 | strong { 14 | font-weight: 500; 15 | } 16 | 17 | a, 18 | button, 19 | input[type="button"], 20 | input[type="reset"], 21 | input[type="submit"] { 22 | color: $link-color; 23 | text-decoration: $link-decoration; 24 | transition: all $link-transition-speed; 25 | } 26 | 27 | ::selection { 28 | background: $color-shuttle-gray; 29 | color: $color-white; 30 | } 31 | 32 | ::-webkit-input-placeholder { 33 | color: $color-heather; 34 | } 35 | 36 | ::-moz-placeholder { 37 | color: $color-heather; 38 | } 39 | 40 | ::placeholder { 41 | color: $color-heather; 42 | } 43 | -------------------------------------------------------------------------------- /themes/default/css/small.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Small device CSS, mostly to fix alignment 3 | */ 4 | 5 | #top { 6 | position: relative; 7 | } 8 | #top .tray { 9 | position: absolute; 10 | right: 10px; 11 | top: 10px; 12 | } 13 | #top #logo, #top ul { 14 | float: none; 15 | } 16 | #top ul li { 17 | padding: 10px 15px 0 0; 18 | } 19 | 20 | .slidey form, .slidey aside { 21 | float: none; 22 | width: 100%; 23 | } 24 | .slidey form { 25 | margin-bottom: 25px; 26 | } 27 | 28 | #comment p, .footnote { 29 | float: none; 30 | width: 100%; 31 | 32 | white-space: normal; 33 | } 34 | 35 | #bottom { 36 | padding: 20px 0; 37 | } 38 | #bottom small { 39 | display: block; 40 | } 41 | #bottom ul { 42 | overflow: hidden; 43 | float: none; 44 | margin-bottom: 15px; 45 | } 46 | #bottom li { 47 | padding: 15px 15px 0 0; 48 | } -------------------------------------------------------------------------------- /anchor/views/assets/js/custom-fields.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extend attribute selection 3 | * 4 | * Show/hide fields depending on type 5 | */ 6 | $(function() { 7 | var select = $('#label-field'), attrs = $('.hide'), fieldtype = $('#label-type'), pagetype = $('#pagetype'); 8 | 9 | var update = function() { 10 | var value = select.val(); 11 | 12 | attrs.hide(); 13 | 14 | if(value == 'image') { 15 | attrs.show(); 16 | } 17 | else if(value == 'file') { 18 | $('.attributes_type').show(); 19 | } 20 | }; 21 | 22 | select.bind('change', update); 23 | 24 | var typechange = function() { 25 | var value = fieldtype.val(); 26 | 27 | if (value == 'page') { 28 | pagetype.parent().show(); 29 | } 30 | else { 31 | pagetype.parent().hide(); 32 | pagetype.val('all'); 33 | } 34 | }; 35 | 36 | fieldtype.bind('change', typechange); 37 | 38 | update(); 39 | }); -------------------------------------------------------------------------------- /anchor/libraries/hash.php: -------------------------------------------------------------------------------- 1 | $rounds]); 21 | } 22 | 23 | /** 24 | * Verifies a hash 25 | * 26 | * @param string $value value to verify the hash against 27 | * @param string $hash hash to check 28 | * 29 | * @return bool whether the hash is valud 30 | */ 31 | public static function check($value, $hash) 32 | { 33 | return password_verify($value, $hash); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /anchor/views/categories/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | 9 |
10 | 11 |
12 | 23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /themes/default/footer.php: -------------------------------------------------------------------------------- 1 |
2 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /anchor/functions/metadata.php: -------------------------------------------------------------------------------- 1 | abs($color-brightness - $dark-text-brightness), $light, $dark); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /anchor/language/en_GB/categories.php: -------------------------------------------------------------------------------- 1 | 'Category', 5 | 'categories' => 'Categories', 6 | 7 | 'create_category' => 'Create a new category', 8 | 'edit_category' => 'Editing “%s”', 9 | 10 | // form fields 11 | 'title' => 'Title', 12 | 'title_explain' => 'Your category title.', 13 | 'title_missing' => 'Please enter a title, it must be a minimum of 3 characters.', 14 | 15 | 'slug' => 'Slug', 16 | 'slug_explain' => 'The slug for your category.', 17 | 18 | 'description' => 'Description', 19 | 'description_explain' => 'What your category is about.', 20 | 21 | // messages 22 | 'created' => 'Your new category has been added.', 23 | 'updated' => 'Your category has been updated.', 24 | 'deleted' => 'Your category has been deleted.', 25 | 'delete_error' => 'You must have at least one category.', 26 | 27 | ]; 28 | -------------------------------------------------------------------------------- /install/storage/htaccess.distro: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteBase {{base}} 4 | 5 | # Allow any files or directories that exist to be displayed directly 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteCond %{REQUEST_FILENAME} !-d 8 | 9 | # Rewrite all other URLs to index.php/URL 10 | RewriteRule ^(.*)$ {{index}} [L] 11 | 12 | #Rewrite anchor directories to index.php/URL even though they exist. 13 | #Don't rewrite files so that we can still load CSS, etc (except .log files). 14 | RewriteCond %{REQUEST_FILENAME} -f 15 | RewriteCond %{REQUEST_URI} !\.log$ 16 | RewriteRule .* - [S=5] 17 | 18 | RewriteRule ^(system(?:$|\/.*$)) {{index}} [L] 19 | RewriteRule ^(anchor(?:$|\/.*$)) {{index}} [L] 20 | RewriteRule ^(content(?:$|\/.*$)) {{index}} [L] 21 | RewriteRule ^(themes(?:$|\/.*$)) {{index}} [L] 22 | RewriteRule ^(vendor(?:$|\/.*$)) {{index}} [L] 23 | 24 | 25 | 26 | ErrorDocument 404 index.php 27 | 28 | -------------------------------------------------------------------------------- /anchor/migrations/200_create_user_meta_table.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ( 12 | `id` int(6) NOT NULL AUTO_INCREMENT, 13 | `user` int(6) NOT NULL, 14 | `extend` int(6) NOT NULL, 15 | `data` text NOT NULL, 16 | PRIMARY KEY (`id`), 17 | KEY `item` (`user`), 18 | KEY `extend` (`extend`) 19 | ) ENGINE=InnoDB"; 20 | 21 | DB::ask($sql); 22 | } 23 | 24 | $table2 = Base::table('extend'); 25 | 26 | if ($this->has_table($table2)) { 27 | $sql2 = "ALTER TABLE `" . $table2 . "` CHANGE `type` `type` ENUM('post','page','category','user') NOT NULL"; 28 | DB::ask($sql2); 29 | } 30 | } 31 | 32 | public function down() 33 | { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /anchor/views/users/amnesia.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 | 7 |
8 |

9 | 'label-email', 11 | 'autocapitalize' => 'off', 12 | 'autofocus' => 'true', 13 | 'placeholder' => __('users.email') 14 | ]); ?>

15 | 16 |

17 | 18 | 19 |

20 |
21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /anchor/views/users/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | 7 | 10 | 11 |
12 | 13 |
14 | 15 | 27 | 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /anchor/views/extend/pagetypes/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | 9 |
10 | 11 |
12 | 13 | = 1): ?> 14 | 24 | 25 |

26 | 27 |

28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | global module, 5 | exports 6 | */ 7 | 8 | /** 9 | * Provides configuration to Jest 10 | */ 11 | module.exports = { 12 | 13 | // set the verbose mode to the value of the DEBUG environment variable 14 | verbose: !!process.env.DEBUG || !!process.env.CI, 15 | 16 | // Coverage won't be working, since we're not testing JS here 17 | collectCoverage: false, 18 | 19 | globals: { 20 | 21 | // the base URL AnchorCMS is running at 22 | __BASE_URL__: 'http://localhost', 23 | 24 | // set the DEBUG environment variable 25 | __DEBUG__: !!process.env.DEBUG 26 | }, 27 | 28 | globalSetup: './test/setup.js', 29 | globalTeardown: './test/teardown.js', 30 | testEnvironment: './test/environment.js', 31 | 32 | // do not send notifications if the tests are carried out in a CI environment 33 | notify: !process.env.CI, 34 | 35 | // only match tests within the integration test folder 36 | testMatch: [ 37 | '**/test/integration/**/*.js' 38 | ] 39 | }; 40 | -------------------------------------------------------------------------------- /anchor/routes/menu.php: -------------------------------------------------------------------------------- 1 | 'auth,install_exists'], function () { 8 | 9 | /** 10 | * List Menu Items 11 | */ 12 | Route::get('admin/menu', function () { 13 | $vars['pages'] = Page::where('show_in_menu', '=', 1) 14 | ->sort('menu_order') 15 | ->get(); 16 | 17 | return View::create('menu/index', $vars) 18 | ->partial('header', 'partials/header') 19 | ->partial('footer', 'partials/footer'); 20 | }); 21 | 22 | /** 23 | * Update order 24 | */ 25 | Route::post('admin/menu/update', function () { 26 | $sort = Input::get('sort'); 27 | 28 | foreach ($sort as $index => $id) { 29 | Page::where('id', '=', $id) 30 | ->update(['menu_order' => $index]); 31 | } 32 | 33 | return Response::json(['result' => true]); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # os/app generated files 2 | Thumbs.db 3 | .DS_Store 4 | .htaccess 5 | *.esproj 6 | *.log 7 | *.gz 8 | *.json 9 | *.txt 10 | .sass-cache/ 11 | .phpintel/ 12 | .idea 13 | *.sublime-project 14 | *.sublime-workspace 15 | 16 | # generated config 17 | /anchor/config/app.php 18 | /anchor/config/db.php 19 | /anchor/config/session.php 20 | 21 | # user mod_rewrite 22 | /.htaccess 23 | 24 | # debug and custom themes 25 | /themes/* 26 | !/themes/default 27 | 28 | # uploaded content 29 | /content/* 30 | !/content/index.php 31 | 32 | # plugins 33 | /plugins/* 34 | 35 | # phpunit tests 36 | /tests 37 | 38 | # We all love Sublime Text and its awesome plugins, don't we? 39 | sftp-config.json 40 | 41 | # User robots.txt 42 | robots.txt 43 | 44 | # User humans.txt [http://humanstxt.org/] 45 | humans.txt 46 | 47 | # Composer vendor 48 | vendor/ 49 | 50 | # Don't want to commit .lock files 51 | *.lock 52 | 53 | # Don't commit the updater files 54 | /anchor_update/ 55 | /anchor_update/* 56 | 57 | # Assets and packages 58 | node_modules/ 59 | *.map 60 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/mixins/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | $breakpoint-defaults: ( 2 | 'default media': all, 3 | 'default feature': min-width, 4 | 'default font size': 16px 5 | ); 6 | 7 | @function em($pixels, $context: map-get($breakpoint-defaults, 'default font size')) { 8 | @return #{$pixels/$context}em; 9 | } 10 | 11 | /// 12 | /// creates a breakpoint 13 | /// 14 | /// @param $measure - breakpoint size 15 | /// @param $feature [min-width] - breakpoint feature 16 | /// @param $medium [all] - media type 17 | /// 18 | @mixin breakpoint( 19 | $measure, 20 | $feature: map-get($breakpoint-defaults, 'default feature'), 21 | $medium: map-get($breakpoint-defaults, 'default media') 22 | ) { 23 | $value: $measure; 24 | 25 | @if (unit($measure) == 'px') { 26 | $value: em($measure); 27 | } 28 | 29 | $queryString: "#{$medium} and (#{$feature}: #{$value})"; 30 | 31 | @media #{$queryString} { 32 | @content; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

You searched for “”.

4 | 5 | 6 | 17 | 18 | 19 | 25 | 26 | 27 | 28 |

Unfortunately, there's no results for “”. Did you spell everything correctly?

29 | 30 | 31 | -------------------------------------------------------------------------------- /anchor/views/assets/js/redirect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Toggles the redirect field in pages 3 | */ 4 | $(function() { 5 | var fieldset = $('fieldset.redirect'), 6 | input = $('input[name=redirect]'), 7 | btn = $('button.secondary.redirector'); 8 | 9 | var toggle = function() { 10 | fieldset.toggleClass('show'); 11 | if (fieldset.hasClass('show')) { 12 | input.removeAttr('tabindex'); 13 | } else { 14 | input.attr('tabindex', '-1'); 15 | } 16 | return false; 17 | }; 18 | 19 | btn.bind('click', toggle); 20 | 21 | // Hide the input if you get rid of the content within. 22 | input.change(function(){ 23 | if(input.val() === '') fieldset.removeClass('show'); 24 | }); 25 | 26 | // Show the redirect field if it isn't empty. 27 | if(input.val() !== '') { 28 | fieldset.addClass('show'); 29 | } 30 | 31 | //If the input is hidden, it shouldn't be possible to tab to it. 32 | if (!input.hasClass('show')) { 33 | input.attr('tabindex', -1); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /anchor/views/extend/variables/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | 9 |
10 | 11 |
12 | 13 | 14 | 24 | 25 |

26 | 27 |

28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /system/start.php: -------------------------------------------------------------------------------- 1 | dispatch(); 42 | 43 | // Update session 44 | Session::close(); 45 | 46 | // Output stuff 47 | $response->send(); 48 | -------------------------------------------------------------------------------- /test/peridot.php: -------------------------------------------------------------------------------- 1 | on('peridot.start', function (Environment $environment) { 10 | 11 | // set constants used in Anchor 12 | define('DS', DIRECTORY_SEPARATOR); 13 | define('ENV', getenv('APP_ENV')); 14 | define('PATH', __DIR__ . DS . '..' . DS); 15 | define('APP', PATH . 'anchor' . DS); 16 | define('SYS', PATH . 'system' . DS); 17 | define('EXT', '.php'); 18 | 19 | // load the Anchor autoloader 20 | require_once(__DIR__ . '/../system/autoloader.php'); 21 | 22 | // register the Anchor autoloader 23 | spl_autoload_register(['System\\Autoloader', 'load']); 24 | 25 | // set the base path to search 26 | System\Autoloader::directory(__DIR__ . '/..'); 27 | 28 | // set the Peridot path to the unit test folder 29 | $environment->getDefinition() 30 | ->getArgument('path') 31 | ->setDefault('test/unit'); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /anchor/models/base.php: -------------------------------------------------------------------------------- 1 | apply(get_called_class()); 25 | 26 | if (method_exists($obj, $method)) { 27 | return call_user_func_array([$obj, $method], $arguments); 28 | } 29 | } 30 | 31 | /** 32 | * @param string|null $name 33 | * 34 | * @return string 35 | */ 36 | public static function table($name = null) 37 | { 38 | if (is_null(static::$prefix)) { 39 | static::$prefix = Config::db('prefix', ''); 40 | } 41 | 42 | if ( ! is_null($name)) { 43 | return static::$prefix . $name; 44 | } 45 | 46 | return static::$prefix . static::$table; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/travis-ci/apache_vhost: -------------------------------------------------------------------------------- 1 | # Configuration file for Apache running on Travis. 2 | # PHP setup in FCGI mode 3 | 4 | 5 | 6 | DocumentRoot %TRAVIS_BUILD_DIR% 7 | 8 | LogLevel info 9 | ErrorLog "%TRAVIS_BUILD_DIR%/apache_error.log" 10 | CustomLog "%TRAVIS_BUILD_DIR%/apache_access.log" combined 11 | 12 | 13 | Options FollowSymLinks MultiViews ExecCGI 14 | AllowOverride All 15 | Require all granted 16 | 17 | # needed for basic auth (PHP_AUTH_USER and PHP_AUTH_PW) 18 | # RewriteEngine on 19 | # RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | # RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}] 21 | 22 | 23 | # Wire up Apache to use Travis CI's php-fpm. 24 | 25 | AddHandler php5-fcgi .php 26 | Action php5-fcgi /php5-fcgi 27 | Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi 28 | FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization 29 | 30 | 31 | Require all granted 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /anchor/functions/config.php: -------------------------------------------------------------------------------- 1 | $value]; 21 | } 22 | 23 | // existing options 24 | $current = Config::get('theme', []); 25 | 26 | // merge theme config 27 | Config::set('theme', array_merge($current, $options)); 28 | } 29 | 30 | /** 31 | * Retrieves a theme option 32 | * 33 | * @param string $option name of the option to retrieve 34 | * @param string $default (optional) fallback value for missing option 35 | * 36 | * @return mixed|string option value if found, default if provided, empty string otherwise 37 | */ 38 | function theme_option($option, $default = '') 39 | { 40 | return Config::get('theme.' . $option, $default); 41 | } 42 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_fileUpload.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | 3 | #media { 4 | display: none; 5 | } 6 | 7 | #upload-file { 8 | position: fixed; 9 | left: 0; 10 | right: 0; 11 | top: 0; 12 | bottom: 0; 13 | z-index: -1; 14 | opacity: 0; 15 | background: rgba($color-heather, 0.75); 16 | transition: opacity $transition-speed-base, background $transition-speed-base; 17 | 18 | &.success { 19 | background: $color-sushi; 20 | 21 | span { 22 | color: $color-white; 23 | background-image: url($icon-check-mark); 24 | } 25 | } 26 | 27 | .draggy & { 28 | opacity: 1; 29 | z-index: 10000; 30 | } 31 | 32 | span { 33 | position: absolute; 34 | left: 50%; 35 | top: 50%; 36 | width: 250px; 37 | height: 30px; 38 | margin: -40px 0 0 -125px; 39 | padding-top: 50px; 40 | font-weight: bold; 41 | font-size: 17px; 42 | line-height: 30px; 43 | text-align: center; 44 | color: $color-slate-gray; 45 | background: url($icon-cloud) no-repeat 50% 0; 46 | transition: color $transition-speed-base; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /anchor/views/extend/fields/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 | 6 | 10 |
11 | 12 |
13 | 14 | results)): ?> 15 | 25 | 26 | 27 | 28 |

29 | 30 |

31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /anchor/libraries/csrf.php: -------------------------------------------------------------------------------- 1 | =5.6", 32 | "ircmaxell/random-lib": "^1.1", 33 | "ircmaxell/password-compat": "^1.0", 34 | "indigophp/hash-compat": "^1.1" 35 | }, 36 | "require-dev": { 37 | "peridot-php/peridot": "^1.19", 38 | "peridot-php/leo": "^1.6" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /anchor/views/extend/pagetypes/add.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |

13 | 14 | 15 | 16 |

17 | 18 |

19 | 20 | 21 | 22 |

23 |
24 | 25 | 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /anchor/migrations/201_enable_pagetypes.php: -------------------------------------------------------------------------------- 1 | has_table($table)) { 11 | $sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` ( 12 | `key` varchar(32) NOT NULL, 13 | `value` varchar(32) NOT NULL 14 | ) ENGINE=InnoDB"; 15 | 16 | DB::ask($sql); 17 | Query::table($table)->insert(array( 18 | 'key' => 'all', 19 | 'value' => 'All Pages' 20 | )); 21 | } 22 | 23 | $table2 = Base::table('extend'); 24 | 25 | if (!$this->has_table_column($table2, 'pagetype')) { 26 | $sql2 = "ALTER TABLE `" . $table2 . "` ADD `pagetype` VARCHAR(140) NOT NULL DEFAULT 'all' AFTER `type`"; 27 | DB::ask($sql2); 28 | } 29 | 30 | $table3 = Base::table('pages'); 31 | 32 | if (!$this->has_table_column($table3, 'pagetype')) { 33 | $sql2 = "ALTER TABLE `" . $table3 . "` ADD `pagetype` VARCHAR(140) NOT NULL DEFAULT 'all' AFTER `slug`"; 34 | DB::ask($sql2); 35 | } 36 | } 37 | 38 | public function down() 39 | { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /anchor/language/en_GB/comments.php: -------------------------------------------------------------------------------- 1 | 'Comments', 5 | 'nocomments_desc' => 'No comments yet.', 6 | 'editing_comment' => 'Editing comment', 7 | 'view_comment' => 'View comment', 8 | 9 | // form fields 10 | 'name' => 'Name', 11 | 'name_explain' => 'Author name', 12 | 'name_missing' => 'Please enter a name', 13 | 14 | 'email' => 'Email address', 15 | 'email_explain' => 'Author email', 16 | 'email_missing' => 'Please enter a valid email address', // frontend message (appears on your site!) 17 | 18 | 'text' => 'Comment', 19 | 'text_explain' => '', 20 | 'text_missing' => 'Please enter comment text', // frontend message (appears on your site!) 21 | 22 | 'status' => 'Status', 23 | 'status_explain' => '', 24 | 25 | // messages 26 | 'created' => 'Your comment has been added', // frontend message (appears on your site!) 27 | 'updated' => 'Your comment has been updated', 28 | 'deleted' => 'Your comment has been deleted', 29 | 30 | // email notification 31 | 'notify_subject' => 'New comment has been added', 32 | 'nofity_heading' => 'A new comment has been submitted to your site.' 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | 3 | .btn { 4 | display: inline-block; 5 | padding: 0 22px; 6 | border: none; 7 | border-radius: $border-radius; 8 | font-size: $font-size-small; 9 | line-height: 38px; 10 | font-weight: bold; 11 | color: $button-color; 12 | background: $button-background; 13 | 14 | /* span.btn */ 15 | cursor: pointer; 16 | 17 | &:hover { 18 | background: $button-background-hover; 19 | } 20 | 21 | &.secondary { 22 | background: $button-secondary-background; 23 | 24 | &:hover { 25 | background: $button-secondary-background-hover; 26 | } 27 | } 28 | 29 | &.blue { 30 | background: $color-blue; 31 | 32 | &:hover { 33 | background: $color-blue-accent; 34 | } 35 | } 36 | 37 | &.red { 38 | background: $color-red; 39 | 40 | &:hover { 41 | background: $color-red-accent; 42 | } 43 | } 44 | 45 | &#exit-focus { 46 | position: fixed; 47 | right: 20px; 48 | top: 20px; 49 | padding: 0 17px; 50 | line-height: 32px; 51 | background: $color-mischka; 52 | color: $color-slate-gray; 53 | 54 | &:hover { 55 | background: $color-mischka; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /anchor/views/extend/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 | 9 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /anchor/views/assets/js/focus-mode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Focus mode for post and page main textarea 3 | */ 4 | $(function() { 5 | var doc = $(document), html = $('html'), body = html.children('body'); 6 | 7 | var Focus = { 8 | // Our element to focus 9 | target: $('textarea[name=html], textarea[name=content]'), 10 | exitSpan: '#exit-focus', 11 | 12 | enter: function() { 13 | html.addClass('focus'); 14 | 15 | if( ! body.children(Focus.exitSpan).length) { 16 | body.append('Exit focus mode (ESC)'); 17 | } 18 | 19 | body.children(Focus.exitSpan).css('opacity', 0).animate({opacity: 1}, 250); 20 | 21 | // Set titles and placeholders 22 | Focus.target.placeholder = (Focus.target.placeholder || '').split('.')[0] + '.'; 23 | }, 24 | 25 | exit: function() { 26 | body.children(Focus.exitSpan).animate({opacity: 0}, 250); 27 | html.removeClass('focus'); 28 | } 29 | }; 30 | 31 | // Bind textarea events 32 | Focus.target.focus(Focus.enter).blur(Focus.exit); 33 | 34 | // Bind key events 35 | doc.on('keyup', function(event) { 36 | // Pressing the "f" key 37 | if(event.keyCode == 70) { 38 | Focus.enter(); 39 | } 40 | 41 | // Pressing the Escape key 42 | if(event.keyCode == 27) { 43 | Focus.exit(); 44 | } 45 | }); 46 | }); -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_notifications.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | 3 | .notifications { 4 | 5 | .notice, 6 | .error, 7 | .success { 8 | padding: 10px 18px; 9 | line-height: 21px; 10 | font-size: $font-size-base; 11 | font-weight: $font-weight-bold; 12 | } 13 | 14 | .notice { 15 | color: $color-white; 16 | background: $color-notification-notice; 17 | } 18 | 19 | .error { 20 | color: $color-white; 21 | background: $color-notification-error; 22 | } 23 | 24 | .success { 25 | color: $color-white; 26 | background: $color-notification-success; 27 | } 28 | 29 | .header & { 30 | position: absolute; 31 | left: 55%; 32 | top: 82px; 33 | width: 320px; 34 | z-index: 1200; 35 | 36 | div::after { 37 | content: ''; 38 | position: absolute; 39 | top: -6px; 40 | right: 50px; 41 | display: block; 42 | border-bottom: 6px solid $color-notification-success; 43 | border-left: 6px solid transparent; 44 | border-right: 6px solid transparent; 45 | } 46 | 47 | .error::after { 48 | border-bottom-color: $color-notification-error; 49 | } 50 | } 51 | 52 | .header .page & { 53 | left: 48%; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/unit/system.request.spec.php: -------------------------------------------------------------------------------- 1 | 'GET', 10 | 'SERVER_PROTOCOL' => 'HTTP/1.1', 11 | 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' 12 | ]; 13 | 14 | global $_SERVER; 15 | 16 | describe('request', function () { 17 | it('should get the request method', function () { 18 | global $_SERVER; 19 | 20 | expect(request::method()) 21 | ->to->equal('GET'); 22 | }); 23 | 24 | it('should get the request protocol', function () { 25 | global $_SERVER; 26 | 27 | expect(request::protocol()) 28 | ->to->equal('HTTP/1.1'); 29 | }); 30 | 31 | it('should determine whether the current request is an AJAX request', function () { 32 | global $_SERVER; 33 | 34 | expect(request::ajax()) 35 | ->to->be->true; 36 | 37 | $_SERVER['HTTP_X_REQUESTED_WITH'] = 'Something else'; 38 | 39 | expect(request::ajax()) 40 | ->to->be->false; 41 | }); 42 | 43 | it( 44 | 'should determine whether the current request has been created on the command line', 45 | function () { 46 | expect(request::cli()) 47 | ->to->be->true; 48 | } 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /anchor/libraries/date.php: -------------------------------------------------------------------------------- 1 | setTimezone(new DateTimeZone(Config::app('timezone'))); 34 | 35 | return $date->format($format); 36 | } 37 | 38 | /** 39 | * All database dates are stored as GMT 40 | * 41 | * @param string $date 42 | * 43 | * @return string 44 | */ 45 | public static function mysql($date) 46 | { 47 | $date = new DateTime($date, new DateTimeZone('GMT')); 48 | 49 | return $date->format('Y-m-d H:i:s'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /anchor/views/users/login.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |
8 | 9 | 10 |
11 |

12 | 'label-user', 14 | 'autocapitalize' => 'off', 15 | 'autofocus' => 'true', 16 | 'placeholder' => __('users.username') 17 | ]); ?>

18 | 19 |

20 | 'pass', 22 | 'placeholder' => __('users.password'), 23 | 'autocomplete' => 'off' 24 | ]); ?>

25 | 26 |

28 | 29 |

30 |
31 |
32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /anchor/views/extend/variables/add.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |

13 | 14 | 'label-name']); ?> 15 | 16 |

17 | 18 |

19 | 20 | 20, 'id' => 'label-value']); ?> 21 | 22 |

23 |
24 | 25 | 30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /anchor/views/menu/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 | 9 | 10 | 17 | 18 |

19 | 20 | No menu items yet. 21 |

22 | 23 |
24 | 25 | 26 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /anchor/views/error/404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 6 | 7 | 14 | 15 | 32 | 33 | 34 | 35 |

404

36 | 37 |

The page was not found.

38 | 39 |

Try the homepage

40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /install/views/error/404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 6 | 7 | 14 | 15 | 32 | 33 | 34 | 35 |

404

36 | 37 |

The page was not found.

38 | 39 |

Try the homepage

40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_editor.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | @import "editor/split"; 3 | @import "editor/half"; 4 | @import "editor/files"; 5 | @import "editor/preview"; 6 | @import "editor/redirect"; 7 | 8 | .header { 9 | position: relative; 10 | background: $color-white; 11 | 12 | input { 13 | float: left; 14 | width: 600px; 15 | padding: 30px 0; 16 | font-size: 29px; 17 | line-height: 40px; 18 | font-weight: lighter; 19 | background: transparent; 20 | } 21 | 22 | .buttons { 23 | float: right; 24 | padding: 30px 0; 25 | 26 | .btn { 27 | margin-left: 8px; 28 | } 29 | } 30 | } 31 | 32 | .main { 33 | padding: 30px 0; 34 | 35 | textarea { 36 | width: 100%; 37 | 38 | &[name=content], 39 | &[name=html] { 40 | padding: 0; 41 | resize: none; 42 | background: transparent; 43 | } 44 | } 45 | } 46 | 47 | .meta { 48 | padding: 30px 0; 49 | background: $color-ghost; 50 | } 51 | 52 | #post-data input[type="checkbox"]:checked, 53 | .split input[type="checkbox"]:checked { 54 | background-image: url($icon-check-mark); 55 | } 56 | 57 | #post-data input[type="file"] + em { 58 | opacity: 1; 59 | } 60 | 61 | #post-data .media-upload textarea { 62 | width: 317px; 63 | min-height: 150px; 64 | max-height: 700px; 65 | resize: vertical; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/editor/_files.scss: -------------------------------------------------------------------------------- 1 | @import "../../variables"; 2 | 3 | .split { 4 | input[type="file"] { 5 | padding: 0; 6 | overflow: hidden; 7 | } 8 | 9 | .file { 10 | float: left; 11 | display: block; 12 | width: 50px; 13 | height: 39px; 14 | border-radius: 0 $border-radius $border-radius 0; 15 | background: $color-athens-gray url($icon-cloud) no-repeat center center; 16 | background-size: 32px; 17 | overflow: hidden; 18 | 19 | input { 20 | display: block; 21 | height: 39px; 22 | opacity: 0; 23 | } 24 | } 25 | 26 | .current-file { 27 | float: left; 28 | display: block; 29 | min-width: 250px; 30 | height: 39px; 31 | padding: 0 16px; 32 | border-radius: 0; 33 | line-height: 39px; 34 | overflow: hidden; 35 | background: $color-white; 36 | } 37 | 38 | .remove-file { 39 | display: block; 40 | float: left; 41 | width: 39px; 42 | height: 39px; 43 | border-radius: 0; 44 | 45 | a { 46 | display: block; 47 | width: 39px; 48 | height: 39px; 49 | text-indent: -1000px; 50 | background: $color-athens-gray url($icon-cross) no-repeat center center; 51 | overflow: hidden; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /anchor/models/user.php: -------------------------------------------------------------------------------- 1 | $value) { 26 | $query->where($key, '=', $value); 27 | } 28 | 29 | return $query->fetch(); 30 | } 31 | 32 | /** 33 | * Paginates the user list 34 | * 35 | * @param int $page page offset 36 | * @param int $perpage page limit 37 | * 38 | * @return \Paginator 39 | * @throws \ErrorException 40 | * @throws \Exception 41 | */ 42 | public static function paginate($page = 1, $perpage = 10) 43 | { 44 | $query = Query::table(static::table()); 45 | $count = $query->count(); 46 | $results = $query 47 | ->take($perpage) 48 | ->skip(($page - 1) * $perpage) 49 | ->sort('real_name', 'desc') 50 | ->get(); 51 | 52 | return new Paginator($results, $count, $page, $perpage, Uri::to('admin/users')); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /anchor/views/assets/js/change-saver.js: -------------------------------------------------------------------------------- 1 | /* Prompts the user if they attempt to leave and there 2 | * are still unsaved changes on important fields 3 | */ 4 | (function($) { 5 | /* first and only argument should be selector for which fields to check within form */ 6 | $.fn.changeSaver = function() { 7 | var form = $(this); 8 | var submitted = false; 9 | var value_store = []; 10 | var field_selector = arguments[0] || "input[type=text], textarea";//by default save all text inputs 11 | 12 | form.find(field_selector).forEach(function(item, index){ 13 | value_store.push({ 14 | element: item, 15 | original_value: $(item).val() 16 | }); 17 | }); 18 | 19 | function hasDiffs() { 20 | for(var i = 0; i < value_store.length; i++){ 21 | var input = value_store[i]; 22 | if (input.original_value != $(input.element).val()) { 23 | return true; 24 | } 25 | } 26 | return false; 27 | } 28 | 29 | $(form).on("submit", function() { 30 | submitted = true; 31 | }); 32 | 33 | $(window).on("beforeunload", function() { 34 | if (!submitted && hasDiffs()) { 35 | return "There are unsaved changes"; 36 | } 37 | }); 38 | }; 39 | }(Zepto)); 40 | -------------------------------------------------------------------------------- /test/unit/system.config.spec.php: -------------------------------------------------------------------------------- 1 | to->be->true; 9 | }); 10 | 11 | it('should use the filename shorthand to access config keys', function () { 12 | expect(config::error('report')) 13 | ->to->be->true; 14 | }); 15 | 16 | it('should get a fallback value for a missing key', function () { 17 | expect(config::get('missing key', 'i did not want that')) 18 | ->to->equal('i did not want that'); 19 | }); 20 | 21 | it('should set new values to the config', function () { 22 | config::set('foo', 42); 23 | 24 | expect(config::get('foo')) 25 | ->to->equal(42); 26 | }); 27 | 28 | it('should set new nested values to the config', function () { 29 | config::set('this.is.a.test', 1.25); 30 | config::set('this.is.another.test', 5.12); 31 | 32 | expect(config::get('this.is.a.test')) 33 | ->to->equal(1.25); 34 | 35 | expect(config::get('this.is.another.test')) 36 | ->to->equal(5.12); 37 | }); 38 | 39 | it('should delete values', function () { 40 | config::erase('error.report'); 41 | 42 | expect(config::error('report')) 43 | ->to->be->null; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/_login.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | /* 4 | Login 5 | */ 6 | body.login { 7 | position: absolute; 8 | top: 25%; 9 | left: 50%; 10 | margin-left: -150px; 11 | background: $color-river-bed; 12 | 13 | .wrap, 14 | .content { 15 | width: 300px; 16 | } 17 | 18 | h1 { 19 | padding-bottom: 25px; 20 | font-size: 25px; 21 | line-height: 60px; 22 | font-weight: lighter; 23 | color: $color-white; 24 | } 25 | 26 | label { 27 | display: none; 28 | } 29 | 30 | fieldset { 31 | border: none; 32 | } 33 | 34 | input { 35 | width: 300px; 36 | margin-bottom: 20px; 37 | padding: 14px 16px; 38 | } 39 | 40 | #logo { 41 | padding: 0; 42 | } 43 | 44 | .buttons { 45 | a { 46 | float: right; 47 | font-size: $font-size-base; 48 | line-height: 38px; 49 | font-weight: $font-weight-bold; 50 | } 51 | 52 | button { 53 | float: left; 54 | 55 | a { 56 | color: $color-regent-gray; 57 | 58 | &:hover { 59 | color: $color-white; 60 | } 61 | } 62 | } 63 | } 64 | 65 | button { 66 | background: $color-oxford-blue; 67 | color: $color-gull-gray; 68 | font-size: $font-size-base; 69 | font-weight: $font-weight-bold; 70 | } 71 | 72 | .notification { 73 | width: 300px; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /system/request.php: -------------------------------------------------------------------------------- 1 | > ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 20 | echo "always_populate_raw_post_data = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 21 | 22 | sudo sed -i -e "s,www-data,travis,g" /etc/apache2/envvars 23 | sudo chown -R travis:travis /var/lib/apache2/fastcgi 24 | 25 | ~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm 26 | -------------------------------------------------------------------------------- /anchor/views/comments/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 | 9 | 16 | 17 | count): ?> 18 | 29 | 30 | 31 | 32 | 33 |

34 | 35 | 36 |

37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /install/views/partials/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Installin' Anchor CMS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 | 40 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic design variables used throughout the components 3 | */ 4 | 5 | // colors 6 | @import "colors"; 7 | 8 | // page 9 | $page-background: $color-athens-gray; 10 | $page-color: $color-slate-gray; 11 | 12 | // borders 13 | $border-radius: 5px; 14 | 15 | // fonts 16 | $font-family-serif: serif; 17 | $font-family-sans-serif: "Helvetica Neue", "Open Sans", "DejaVu Sans", "Arial", sans-serif; 18 | $font-family-monospace: Monaco, "Courier New", courier, monospace; 19 | $font-family-base: $font-family-sans-serif; 20 | 21 | // font sizes 22 | $font-size-base: 15px; 23 | $font-size-small: 13px; 24 | 25 | // font weights 26 | $font-weight-base: 400; 27 | $font-weight-bold: 600; 28 | $font-weight-heading: 300; 29 | 30 | // line heights 31 | $line-height-base: 25px; 32 | 33 | // transitions 34 | $transition-speed-base: 0.25s; 35 | $transition-speed-slow: 0.4s; 36 | 37 | // buttons 38 | $button-background: $color-green; 39 | $button-background-hover: $color-green-accent; 40 | $button-color: $color-white; 41 | 42 | $button-secondary-background: $color-gull-gray; 43 | $button-secondary-background-hover: $color-regent-gray; 44 | 45 | // links 46 | $link-color: $color-kashmir-blue; 47 | $link-decoration: none; 48 | $link-transition-speed: $transition-speed-base; 49 | 50 | // icons 51 | $icon-logo: '../img/logo.png'; 52 | $icon-cloud: '../img/cloud.png'; 53 | $icon-cross: '../img/cross.gif'; 54 | $icon-check-mark: '../img/tick.png'; 55 | 56 | // breakpoints 57 | $breakpoint-small: 981px; 58 | -------------------------------------------------------------------------------- /system/database/connectors/sqlite.php: -------------------------------------------------------------------------------- 1 | pdo = new PDO($dns); 58 | $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 59 | } 60 | 61 | /** 62 | * Return the pdo instance 63 | * 64 | * @return object|\PDO PDO object 65 | */ 66 | public function instance() 67 | { 68 | return $this->pdo; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /anchor/views/error/500.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 - Internal Server Error 6 | 7 | 25 | 26 | 43 | 44 | 45 |

Internal Server Error

46 | 47 |

An error occured while we were processing your request.

48 | 49 | 50 | -------------------------------------------------------------------------------- /install/views/error/500.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 - Internal Server Error 6 | 7 | 25 | 26 | 43 | 44 | 45 |

Internal Server Error

46 | 47 |

An error occured while we were processing your request.

48 | 49 | 50 | -------------------------------------------------------------------------------- /install/views/account.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |

Your first account

7 | 8 |

Oh, we're so tantalisingly close! All we need now is a username and password to log in to the admin area 9 | with.

10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |

18 | 19 | You use this to log in. 20 | 21 |

22 | 23 |

24 | 25 | Needed if you can’t log in. 26 | 27 | 28 |

29 | 30 |

31 | 32 | Make sure to pick a secure password. 33 | 34 |

35 |
36 | 37 |
38 | « Back 39 | 40 |
41 |
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | 3 | RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories 4 | 5 | RUN apk update && apk upgrade && \ 6 | apk add bash wget curl apache2 7 | 8 | RUN apk add php7-mbstring php7-mcrypt \ 9 | php7-apache2 php7-openssl php7-curl php7-json \ 10 | php7-pdo php7-pdo_mysql php7-gd \ 11 | php7-intl php7-opcache php7-session 12 | 13 | RUN sed -i "s|display_errors = Off|display_errors = On|" /etc/php7/php.ini && \ 14 | sed -i "s|variables_order = .*|variables_order = EGPCS|" /etc/php7/php.ini && \ 15 | sed -i "s|;cgi.fix_pathinfo=1|cgi.fix_pathinfo=0|" /etc/php7/php.ini && \ 16 | sed -i "s|#LoadModule rewrite_module modules/mod_rewrite.so|LoadModule rewrite_module modules/mod_rewrite.so|" /etc/apache2/httpd.conf && \ 17 | sed -i 's#^DocumentRoot ".*#DocumentRoot "/var/www/html"#g' /etc/apache2/httpd.conf && \ 18 | echo '' >> /etc/apache2/httpd.conf && \ 19 | echo 'Require all granted' >> /etc/apache2/httpd.conf && \ 20 | echo 'AllowOverride FileInfo' >> /etc/apache2/httpd.conf && \ 21 | echo '' >> /etc/apache2/httpd.conf && \ 22 | echo 'HttpProtocolOptions "Unsafe"' >> /etc/apache2/httpd.conf && \ 23 | mkdir /run/apache2/ && \ 24 | ln -sf /dev/null /var/log/apache2/access.log && \ 25 | ln -sf /dev/stderr /var/log/apache2/error.log 26 | 27 | RUN rm -rf /var/cache/apk/* 28 | 29 | WORKDIR /var/www/html 30 | VOLUME ["/var/www/html"] 31 | 32 | EXPOSE 80 33 | 34 | ENTRYPOINT ["/usr/sbin/httpd"] 35 | CMD ["-D", "FOREGROUND"] 36 | -------------------------------------------------------------------------------- /anchor/views/extend/pagetypes/edit.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

key); ?>

5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |

13 | 14 | value)); ?> 15 | 16 |

17 | 18 |

19 | 20 | key)); ?> 21 | 22 |

23 |
24 | 25 | 35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /anchor/language/en_GB/pages.php: -------------------------------------------------------------------------------- 1 | 'Pages', 6 | 7 | 'create_page' => 'Create a new page', 8 | 'nopages_desc' => 'You don’t have any pages.', 9 | 'redirect' => 'Redirect', 10 | 11 | // form fields 12 | 'redirect_url' => 'Redirect Url', 13 | 'redirect_missing' => 'Please enter a valid url', 14 | 15 | 'title' => 'Page title', 16 | 'title_explain' => '', 17 | 'title_missing' => 'The title of this page must be atleast 3 characters', 18 | 19 | 'content' => 'Content', 20 | 'content_explain' => 'Your page’s content. Uses Markdown.', 21 | 22 | 'show_in_menu' => 'Show In Menu', 23 | 'show_in_menu_explain' => '', 24 | 25 | 'name' => 'Name', 26 | 'name_explain' => '', 27 | 28 | 'slug' => 'Slug', 29 | 'slug_explain' => 'Slug uri to identify your page, should only contain ascii characters', 30 | 'slug_missing' => 'The slug must be atleast 3 characters, slugs can only contain ascii characters', 31 | 'slug_duplicate' => 'Slug already exists', 32 | 'slug_invalid' => 'Slug must contain letters', 33 | 34 | 'status' => 'Status', 35 | 'status_explain' => '', 36 | 37 | 'parent' => 'Parent', 38 | 'parent_explain' => '', 39 | 40 | 'pagetype' => 'Page Type', 41 | 'pagetype_explain' => 'Select the page type this page best belongs to.', 42 | 43 | // messages 44 | 'updated' => 'Your page was updated.', 45 | 'created' => 'Your page was created.', 46 | 'deleted' => 'Your page was deleted.' 47 | 48 | ]; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anchor-cms", 3 | "version": "1.12.0", 4 | "description": "Anchor is a super-simple, lightweight blog system, made to let you just write. [Check out the site](http://anchorcms.com/).", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:debug": "env DEBUG=\"puppeteer:error,-puppeteer:protocol\" jest", 9 | "build": "gulp build", 10 | "dev": "gulp build && gulp browsersync" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/anchorcms/anchor-cms.git" 15 | }, 16 | "author": "Moritz Friedrich", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/anchorcms/anchor-cms/issues" 20 | }, 21 | "homepage": "https://github.com/anchorcms/anchor-cms#readme", 22 | "devDependencies": { 23 | "babel-preset-env": "^1.6.1", 24 | "browser-sync": "^2.23.6", 25 | "chalk": "^2.3.1", 26 | "faker": "^4.1.0", 27 | "gulp-autoprefixer": "^4.1.0", 28 | "gulp-babel": "^7.0.1", 29 | "gulp-cache": "^1.0.2", 30 | "gulp-clean-css": "^3.9.2", 31 | "gulp-concat": "^2.6.1", 32 | "gulp-imagemin": "^4.1.0", 33 | "gulp-jshint": "^2.1.0", 34 | "gulp-plumber": "^1.2.0", 35 | "gulp-rename": "^1.2.2", 36 | "gulp-sass": "^3.1.0", 37 | "gulp-scss-lint": "^0.6.1", 38 | "gulp-sourcemaps": "^2.6.4", 39 | "gulp-uglify": "^3.0.0", 40 | "jest": "^22.4.0", 41 | "jest-environment-node": "^22.4.0", 42 | "mkdirp": "^0.5.1", 43 | "normalize.css": "^8.0.0", 44 | "puppeteer": "^1.1.0", 45 | "rimraf": "^2.6.2" 46 | }, 47 | "dependencies": { 48 | "gulp": "^3.9.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /anchor/models/category.php: -------------------------------------------------------------------------------- 1 | sort('title')->get() as $item) { 26 | $items[$item->id] = $item->title; 27 | } 28 | 29 | return $items; 30 | } 31 | 32 | /** 33 | * Retrieves a category by slug 34 | * 35 | * @param string $slug 36 | * 37 | * @return \stdClass 38 | * @throws \Exception 39 | */ 40 | public static function slug($slug) 41 | { 42 | return static::where('slug', 'like', $slug)->fetch(); 43 | } 44 | 45 | /** 46 | * Paginates category results 47 | * 48 | * @param int $page page offset 49 | * @param int $perpage page limit 50 | * 51 | * @return \Paginator 52 | * @throws \ErrorException 53 | * @throws \Exception 54 | */ 55 | public static function paginate($page = 1, $perpage = 10) 56 | { 57 | $query = Query::table(static::table()); 58 | $count = $query->count(); 59 | $results = $query->take($perpage)->skip(($page - 1) * $perpage)->sort('title')->get(); 60 | 61 | return new Paginator($results, $count, $page, $perpage, Uri::to('admin/categories')); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /anchor/views/assets/js/sortable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Zepto sortable plugin using html5 drag and drop api. 3 | */ 4 | ;(function($) { 5 | $.fn.sortable = function(options) { 6 | 7 | var defaults = { 8 | element: 'li', 9 | dropped: function() {} 10 | }; 11 | 12 | var settings = $.extend({}, defaults, options); 13 | var sortables = $(this).find(settings.element); 14 | var dragsrc; 15 | 16 | var dragstart = function(event) { 17 | $(this).addClass('moving'); 18 | 19 | dragsrc = this; 20 | 21 | event.dataTransfer.effectAllowed = 'move'; 22 | event.dataTransfer.setData('text/html', this.innerHTML); 23 | }; 24 | 25 | var dragenter = function() { 26 | $(this).addClass('over'); 27 | } 28 | 29 | var dragleave = function() { 30 | $(this).removeClass('over'); 31 | }; 32 | 33 | var dragover = function(event) { 34 | event.preventDefault(); 35 | event.stopPropagation(); 36 | 37 | event.dataTransfer.dropEffect = 'move'; 38 | }; 39 | 40 | var drop = function(event) { 41 | event.preventDefault(); 42 | event.stopPropagation(); 43 | 44 | if (dragsrc != this) { 45 | dragsrc.innerHTML = this.innerHTML; 46 | 47 | this.innerHTML = event.dataTransfer.getData('text/html'); 48 | } 49 | 50 | settings.dropped(); 51 | }; 52 | 53 | var dragend = function() { 54 | $(this).removeClass('moving'); 55 | sortables.removeClass('over'); 56 | }; 57 | 58 | sortables.on('dragstart', dragstart); 59 | sortables.on('dragenter', dragenter); 60 | sortables.on('dragover', dragover); 61 | sortables.on('dragleave', dragleave); 62 | sortables.on('drop', drop); 63 | sortables.on('dragend', dragend); 64 | }; 65 | }(Zepto)); -------------------------------------------------------------------------------- /test/unit/system.request.server.spec.php: -------------------------------------------------------------------------------- 1 | server = new server([ 8 | 'x' => true, 9 | 'y' => 42, 10 | 'z' => 'test' 11 | ]); 12 | }); 13 | 14 | it('should create an instance', function () { 15 | expect($this->server) 16 | ->to->be->an->instanceof(server::class); 17 | }); 18 | 19 | it('should get values from the server array', function () { 20 | expect($this->server->get('y')) 21 | ->to->equal(42); 22 | }); 23 | 24 | it('should get a fallback value for a missing key', function () { 25 | expect($this->server->get('how does a pirate sound like?', 'HARRR!')) 26 | ->to->equal('HARRR!'); 27 | }); 28 | 29 | it('should set a value to the server array', function () { 30 | $this->server->set('foo', 'bar'); 31 | 32 | expect($this->server->get('foo')) 33 | ->to->equal('bar'); 34 | }); 35 | 36 | it('should check whether a key exists in the server array', function () { 37 | expect($this->server->has('x')) 38 | ->to->be->true(); 39 | 40 | expect($this->server->has('a')) 41 | ->to->be->false(); 42 | }); 43 | 44 | it('should erase values from the server array', function () { 45 | $this->server->set('123', 321); 46 | 47 | expect($this->server->get('123')) 48 | ->to->equal(321); 49 | 50 | $this->server->erase('123'); 51 | 52 | expect($this->server->get('123')) 53 | ->to->be->null(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /themes/default/posts.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 34 | 35 | 36 | 46 | 47 | 48 | 49 |
50 |

No posts yet!

51 |

Looks like you have some writing to do!

52 |
53 | 54 | 55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /anchor/views/extend/variables/edit.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

user_key); ?>

5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |

13 | 14 | user_key), ['id' => 'label-name']); ?> 15 | 16 |

17 | 18 |

19 | 20 | value), 21 | ['cols' => 20, 'id' => 'label-value']); ?> 22 | 23 |

user_key); ?> 24 |

25 |
26 | 27 | 35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /anchor/language/en_GB/posts.php: -------------------------------------------------------------------------------- 1 | 'Posts', 6 | 7 | 'create_post' => 'Create a new post', 8 | 'noposts_desc' => 'You don’t have any posts!', 9 | 10 | // form fields 11 | 'title' => 'Post title', 12 | 'title_explain' => '', 13 | 'title_missing' => 'The title of this post must be atleast 3 characters', 14 | 15 | 'content' => 'Post Content', 16 | 'content_explain' => 'Just write.', 17 | 18 | 'slug' => 'Slug', 19 | 'slug_explain' => 'Slug uri to identify your post, should only contain ascii characters', 20 | 'slug_missing' => 'The slug must be atleast 3 characters, slugs can only contain ascii characters', 21 | 'slug_duplicate' => 'Slug already exists', 22 | 'slug_invalid' => 'Slug must contain letters', 23 | 24 | 'time' => 'Published on (GMT)', 25 | 'time_explain' => 'Pattern: YYYY-MM-DD HH:MM:SS', 26 | 'time_invalid' => 'Invalid time pattern', 27 | 28 | 'description' => 'Description', 29 | 'description_explain' => '', 30 | 31 | 'status' => 'Status', 32 | 'status_explain' => '', 33 | 34 | 'category' => 'Category', 35 | 'category_explain' => '', 36 | 37 | 'allow_comments' => 'Allow Comments', 38 | 'allow_comments_explain' => '', 39 | 40 | 'custom_css' => 'Custom CSS', 41 | 'custom_css_explain' => '', 42 | 43 | 'custom_js' => 'Custom JS', 44 | 'custom_js_explain' => '', 45 | 46 | // messages 47 | 'updated' => 'Your article has been updated', 48 | 'created' => 'Your new article was created', 49 | 'deleted' => 'Your article has been deleted' 50 | 51 | ]; 52 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_lists.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | @import "../mixins/breakpoints"; 3 | 4 | .list { 5 | width: 100%; 6 | border-top: 1px solid $color-geyser; 7 | list-style: none; 8 | 9 | li { 10 | float: none; 11 | width: inherit; 12 | margin-right: 0; 13 | 14 | @include breakpoint($breakpoint-small) { 15 | width: auto; 16 | } 17 | 18 | a { 19 | position: relative; 20 | overflow: hidden; 21 | display: block; 22 | padding: 25px 30px 15px; 23 | border-bottom: 1px solid $color-geyser; 24 | color: $color-slate-gray; 25 | 26 | &:hover { 27 | background: $color-light-solitude; 28 | } 29 | } 30 | 31 | strong { 32 | position: relative; 33 | overflow: hidden; 34 | display: block; 35 | width: 85%; 36 | height: 25px; 37 | white-space: nowrap; 38 | text-overflow: ellipsis; 39 | font-size: 19px; 40 | color: $color-shuttle-gray; 41 | 42 | .status { 43 | position: relative; 44 | top: 10px; 45 | right: 30px; 46 | float: right; 47 | z-index: 10; 48 | } 49 | 50 | & + span { 51 | display: block; 52 | padding-bottom: 8px; 53 | font-size: 13px; 54 | color: $color-hit-gray; 55 | } 56 | } 57 | 58 | p { 59 | display: none; 60 | margin-bottom: 10px; 61 | 62 | @include breakpoint($breakpoint-small) { 63 | display: block; 64 | } 65 | } 66 | 67 | .status { 68 | margin-left: 10px; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /install/libraries/braces.php: -------------------------------------------------------------------------------- 1 | path = $path; 24 | } 25 | 26 | /** 27 | * Shorthand to create and render a template 28 | * 29 | * @param string $path path to the template file 30 | * @param array $vars variables to replace in the template 31 | * 32 | * @return string 33 | */ 34 | public static function compile($path, $vars = []) 35 | { 36 | $braces = new static($path); 37 | 38 | return $braces->render($vars); 39 | } 40 | 41 | /** 42 | * Renders a template by replacing all values in braces 43 | * 44 | * @param array $vars variables to replace in the template 45 | * 46 | * @return string rendered template 47 | */ 48 | public function render($vars = []) 49 | { 50 | $content = file_get_contents($this->path); 51 | 52 | $keys = array_map([$this, 'key'], array_keys($vars)); 53 | $values = array_values($vars); 54 | 55 | return str_replace($keys, $values, $content); 56 | } 57 | 58 | /** 59 | * Creates a braced representation of a variable 60 | * 61 | * @param string $var variable to brace 62 | * 63 | * @return string braced variable 64 | */ 65 | public function key($var) 66 | { 67 | return '{{' . $var . '}}'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/unit/system.error.spec.php: -------------------------------------------------------------------------------- 1 | message = $message; 21 | $this->file = $file; 22 | $this->line = 0; 23 | $this->trace = debug_backtrace(); 24 | } 25 | 26 | public function getMessage() 27 | { 28 | return $this->message; 29 | } 30 | 31 | public function getFile() 32 | { 33 | return $this->file; 34 | } 35 | 36 | public function getLine() 37 | { 38 | return $this->line; 39 | } 40 | 41 | public function getTrace() 42 | { 43 | return $this->trace; 44 | } 45 | } 46 | 47 | describe('error', function () { 48 | # set_exception_handler([error::class, 'exception']); 49 | # set_error_handler([error::class, 'native']); 50 | # register_shutdown_function([error::class, 'shutdown']); 51 | 52 | xit('should handle exceptions (not testable due to exit(1) call)'); 53 | xit('should handle errors (not testable due to exit(1) call)'); 54 | 55 | it('should log errors', function () { 56 | ob_start(); 57 | 58 | error::log(new MockError('test')); 59 | 60 | $output = ob_get_clean(); 61 | 62 | expect(preg_replace('/\s+/', '', $output)) 63 | ->to->equal(preg_replace('/\s+/', '', '
test
64 |      The error has been logged in /anchor/errors.log
')); 65 | 66 | expect(file_exists(APP . 'errors.log')) 67 | ->to->be->true(); 68 | 69 | unlink(APP . 'errors.log'); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | global module, 5 | require 6 | */ 7 | 8 | const chalk = require( 'chalk' ); 9 | const puppeteer = require( 'puppeteer' ); 10 | const fs = require( 'fs' ); 11 | const mkdirp = require( 'mkdirp' ); 12 | const os = require( 'os' ); 13 | const path = require( 'path' ); 14 | const jestConfig = require( '../jest.config' ); 15 | 16 | const baseDirectory = path.join( os.tmpdir(), 'jest_puppeteer_global_setup' ); 17 | 18 | module.exports = async function () { 19 | jestConfig.globals.__DEBUG__ && console.log( chalk.green( 'Setup Puppeteer' ) ); 20 | 21 | const puppeteerArgs = { 22 | args: [ 23 | '--no-sandbox', 24 | '--disable-setuid-sandbox', 25 | '--disable-dev-shm-usage' 26 | ], 27 | headless: !jestConfig.globals.__DEBUG__, 28 | devtools: jestConfig.globals.__DEBUG__ 29 | }; 30 | 31 | if ( jestConfig.globals.__DEBUG__ ) { 32 | puppeteerArgs.slowMo = 500; 33 | } 34 | 35 | const browser = await puppeteer.launch( puppeteerArgs ); 36 | 37 | /** 38 | * Opens a new page and stores a reference 39 | * 40 | * @type {Page} 41 | */ 42 | const page = await browser.newPage(); 43 | 44 | try { 45 | await page.goto( jestConfig.globals.__BASE_URL__ ); 46 | } catch ( error ) { 47 | console.error( chalk.red( `Could not connect to local web server: ${error.message}` ) ); 48 | 49 | if ( jestConfig.globals.__DEBUG__ ) { 50 | console.error( chalk.red( `Failing tests prematurely` ) ); 51 | 52 | return process.exit( 3 ); 53 | } 54 | } 55 | 56 | global.__BROWSER__ = browser; 57 | 58 | mkdirp.sync( baseDirectory ); 59 | 60 | fs.writeFileSync( 61 | path.join( baseDirectory, 'wsEndpoint' ), 62 | browser.wsEndpoint() 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /anchor/language/en_GB/metadata.php: -------------------------------------------------------------------------------- 1 | 'Site Settings', 6 | 'metadata_desc' => 'Manage your site settings', 7 | 8 | 'comment_settings' => 'Comments', 9 | 'theme_settings' => 'Appearance', 10 | 11 | // form fields 12 | 'sitename' => 'Site name', 13 | 'sitename_explain' => '', 14 | 'sitename_missing' => 'Your site needs a name!', 15 | 16 | 'sitedescription' => 'Site description', 17 | 'sitedescription_explain' => 'A description of your site', 18 | 'sitedescription_missing' => 'Your site needs a description!', 19 | 20 | 'homepage' => 'Home Page', 21 | 'homepage_explain' => '', 22 | 23 | 'postspage' => 'Posts Page', 24 | 'postspage_explain' => '', 25 | 26 | 'dashboard_page' => 'Dashboard Page', 27 | 'dashboard_page_explain' => 'Default dashboard page', 28 | 29 | 'posts_per_page' => 'Posts per page', 30 | 'posts_per_page_explain' => '', 31 | 32 | 'show_all_posts' => 'Show all posts', 33 | 'show_all_posts_explain' => 'Grab posts without pagination (could be slow)', 34 | 35 | 'auto_publish_comments' => 'Auto-allow comments', 36 | 'auto_publish_comments_explain' => '', 37 | 38 | 'comment_notifications' => 'Email notification for new comments', 39 | 'comment_notifications_explain' => '', 40 | 41 | 'comment_moderation_keys' => 'Spam keywords', 42 | 'comment_moderation_keys_explain' => 'Comma separated list of keywords to blacklist against. Comments will automatically be set as spam.', 43 | 44 | 'current_theme' => 'Current theme', 45 | 'current_theme_explain' => '', 46 | 47 | // messages 48 | 'updated' => 'Metadata updated', 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /anchor/libraries/events.php: -------------------------------------------------------------------------------- 1 | slug][$name]) ? static::$stack[$page->slug][$name] : false) { 69 | return is_callable($func) ? $func() : ''; 70 | } 71 | 72 | return ''; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /anchor/migrations/211_alter_post_page_content.php: -------------------------------------------------------------------------------- 1 | has_table_column($table, 'content')) { 12 | $sql = 'ALTER TABLE `' . $table . '` '; 13 | $sql .= 'CHANGE `content` `markdown` TEXT'; 14 | DB::ask($sql); 15 | } 16 | 17 | if (!$this->has_table_column($table, 'html') && $this->has_table_column($table, 'markdown')) { 18 | $sql = 'ALTER TABLE `' . $table . '` '; 19 | $sql .= 'ADD `html` TEXT NOT NULL AFTER `markdown`'; 20 | DB::ask($sql); 21 | 22 | $pages = Page::sort('menu_order', 'desc')->get(); 23 | foreach ($pages as $page) { 24 | Page::update($page->id, array( 25 | 'html' => parse($page->markdown) 26 | )); 27 | } 28 | } 29 | 30 | if (!$this->has_table_column($table2, 'markdown') && $this->has_table_column($table2, 'html')) { 31 | $sql = 'ALTER TABLE `' . $table2 . '` '; 32 | $sql .= 'ADD `markdown` TEXT NOT NULL AFTER `description`'; 33 | DB::ask($sql); 34 | 35 | $migrate_data_sql = 'update `' . $table2 . '` set `markdown` = `html`, `html` = "";'; 36 | DB::ask($migrate_data_sql); 37 | 38 | $posts = Post::sort('created', 'desc')->get(); 39 | foreach ($posts as $post) { 40 | Post::update($post->id, array( 41 | 'html' => parse($post->markdown) 42 | )); 43 | } 44 | } 45 | } 46 | 47 | public function down() 48 | { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/_colors.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Color definitions: 3 | * Enables the usage of named, well-known colors. 4 | */ 5 | $color-black: #000; 6 | $color-sushi: #77b52f; 7 | $color-olive-drab: #6aad28; 8 | $color-vida-loca: #4f8c12; 9 | $color-prairie-sand: #a03621; 10 | $color-mojo: #c4533d; 11 | $color-valencia: #d34937; 12 | $color-cadet-blue: #aab3c0; 13 | $color-danube: #7599d2; 14 | $color-havelock-blue: #578cd9; 15 | $color-san-marino: #5479b2; 16 | $color-kashmir-blue: #486899; 17 | $color-bright-gray: #38414f; 18 | $color-licorice: #2f3744; 19 | $color-oxford-blue: #394556; 20 | $color-river-bed: #444f5f; 21 | $color-trout: #4a525f; 22 | $color-shuttle-gray: #606b7b; 23 | $color-nevada: #616a71; 24 | $color-lynch: #687993; 25 | $color-slate-gray: #6a788d; 26 | $color-light-slate-gray: #7d8693; 27 | $color-bali-hai: #8191ab; 28 | $color-regent-gray: #848f9f; 29 | $color-light-regent-gray: #8491a5; 30 | $color-feather: #95a1b2; 31 | $color-gray-chateau: #99a3b1; 32 | $color-gull-gray: #a4adbb; 33 | $color-hit-gray: #a3acb9; 34 | $color-rock-blue: #a4b4cb; 35 | $color-aluminium: #aab1bc; 36 | $color-heather: #b2bed0; 37 | $color-silver: #ccc; 38 | $color-ghost: #ccd2d9; 39 | $color-mischka: #cdd3db; 40 | $color-geyser: #dadfe5; 41 | $color-solitude: #e6e9ed; 42 | $color-light-solitude: #e7eaee; 43 | $color-athens-gray: #eceef1; 44 | $color-alabaster: #f9f9f9; 45 | $color-white: #fff; 46 | 47 | /** 48 | * Color name aliases 49 | */ 50 | $color-green: $color-olive-drab; 51 | $color-green-accent: $color-vida-loca; 52 | $color-blue: $color-danube; 53 | $color-blue-accent: $color-san-marino; 54 | $color-red: $color-mojo; 55 | $color-red-accent: $color-prairie-sand; 56 | $color-notification-success: $color-olive-drab; 57 | $color-notification-error: $color-valencia; 58 | $color-notification-notice: $color-havelock-blue; 59 | -------------------------------------------------------------------------------- /anchor/views/assets/js/autosave.js: -------------------------------------------------------------------------------- 1 | /** 2 | * From @TheBrenny: 3 | * This JS File holds the functions that are used to determine whether or 4 | * not we should turn on/off autosave, and determines whether or not 5 | * autosave is active. 6 | */ 7 | 8 | $(document).ready(function() { 9 | var autosaveInterval; 10 | var maxSeconds = 30; 11 | var secondsPassed = 0; 12 | 13 | var onInterval = function() { 14 | secondsPassed++; 15 | if(secondsPassed > maxSeconds) { 16 | secondsPassed = 0; 17 | submitDocument(); 18 | } 19 | $(".autosave-label").text("Autosave in " + (maxSeconds - secondsPassed)); 20 | }; 21 | 22 | var submitDocument = function() { 23 | $("form").first().trigger("submit"); 24 | }; 25 | 26 | var alterAutosaveActionButton = function() { 27 | var pressOn = (autosaveInterval !== null); 28 | $(".autosave-action").toggleClass("green", pressOn); 29 | $(".autosave-action").toggleClass("autosave-on", pressOn); 30 | $(".autosave-action").toggleClass("secondary", !pressOn); 31 | $(".autosave-label").text(pressOn ? "Autosave in 30" : "Autosave: Off"); 32 | /* 33 | if(pressOn) { // Just turned on autosave 34 | $(".autosave-action").addClass("green"); 35 | $(".autosave-action").removeClass("secondary"); 36 | $(".autosave-label").text("Autosave in 30"); 37 | } else { // Just turned off autosave 38 | $(".autosave-action").addClass("secondary"); 39 | $(".autosave-action").removeClass("green"); 40 | $(".autosave-label").text("Autosave: Off"); 41 | } 42 | */ 43 | }; 44 | 45 | $(".autosave-action").click(function() { 46 | if(autosaveInterval === null) { 47 | autosaveInterval = setInterval(function() {onInterval();}, 1000); 48 | } else { 49 | clearInterval(autosaveInterval); 50 | autosaveInterval = null; 51 | secondsPassed = 0; 52 | } 53 | alterAutosaveActionButton(); 54 | }); 55 | }); -------------------------------------------------------------------------------- /anchor/libraries/items.php: -------------------------------------------------------------------------------- 1 | position = 0; 31 | $this->array = $items; 32 | } 33 | 34 | /** 35 | * Retrieves the current item 36 | * 37 | * @return mixed 38 | */ 39 | public function current() 40 | { 41 | return $this->array[$this->position]; 42 | } 43 | 44 | /** 45 | * Retrieves the current key 46 | * 47 | * @return int 48 | */ 49 | public function key() 50 | { 51 | return $this->position; 52 | } 53 | 54 | /** 55 | * Increments the position 56 | * 57 | * @return void 58 | */ 59 | public function next() 60 | { 61 | ++$this->position; 62 | } 63 | 64 | /** 65 | * Sets the position to the beginning 66 | * 67 | * @return void 68 | */ 69 | public function rewind() 70 | { 71 | $this->position = 0; 72 | } 73 | 74 | /** 75 | * Checks whether the current offset exists 76 | * 77 | * @return bool 78 | */ 79 | public function valid() 80 | { 81 | return isset($this->array[$this->position]); 82 | } 83 | 84 | /** 85 | * Retrieves the length 86 | * 87 | * @return int 88 | */ 89 | public function length() 90 | { 91 | return count($this->array); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | global module, 5 | require 6 | */ 7 | 8 | const chalk = require( 'chalk' ); 9 | const NodeEnvironment = require( 'jest-environment-node' ); 10 | const puppeteer = require( 'puppeteer' ); 11 | const fs = require( 'fs' ); 12 | const os = require( 'os' ); 13 | const path = require( 'path' ); 14 | const jestConfig = require( '../jest.config' ); 15 | 16 | const baseDirectory = path.join( os.tmpdir(), 'jest_puppeteer_global_setup' ); 17 | 18 | /** 19 | * Provides the global environment to puppeteer 20 | */ 21 | class Environment extends NodeEnvironment { 22 | 23 | /** 24 | * Creates a new environment 25 | * 26 | * @param {object} config 27 | */ 28 | constructor ( config ) { 29 | super( config ); 30 | } 31 | 32 | /** 33 | * Sets up the test environment 34 | * 35 | * @return {Promise} 36 | */ 37 | async setup () { 38 | jestConfig.globals.__DEBUG__ && console.log( chalk.yellow( 'Setup Test Environment' ) ); 39 | 40 | await super.setup(); 41 | 42 | const wsEndpoint = fs.readFileSync( path.join( baseDirectory, 'wsEndpoint' ), 'utf8' ); 43 | 44 | if ( !wsEndpoint ) { 45 | throw new Error( 'wsEndpoint not found' ); 46 | } 47 | 48 | this.global.__BROWSER__ = await puppeteer.connect( { browserWSEndpoint: wsEndpoint } ); 49 | } 50 | 51 | /** 52 | * Tears down the test environment 53 | * 54 | * @return {Promise} 55 | */ 56 | async teardown () { 57 | jestConfig.globals.__DEBUG__ && console.log( chalk.yellow( 'Teardown Test Environment' ) ); 58 | 59 | return await super.teardown(); 60 | } 61 | 62 | /** 63 | * Runs the test script 64 | * 65 | * @param {*} script 66 | * @return {*} 67 | */ 68 | runScript ( script ) { 69 | return super.runScript( script ); 70 | } 71 | } 72 | 73 | module.exports = Environment; 74 | -------------------------------------------------------------------------------- /anchor/views/assets/scss/components/_header.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | @import "../mixins/breakpoints"; 3 | 4 | .top { 5 | position: relative; 6 | padding: 15px 0; 7 | background: $color-river-bed; 8 | z-index: 21; 9 | 10 | a { 11 | display: inline-block; 12 | padding: 0 18px; 13 | font-size: $font-size-small; 14 | font-weight: $font-weight-bold; 15 | line-height: 34px; 16 | } 17 | 18 | .logo { 19 | margin-right: 25px; 20 | 21 | a { 22 | padding: 0; 23 | background: transparent url($icon-logo) no-repeat center center; 24 | width: 24px; 25 | height: 34px; 26 | text-indent: -1000px; 27 | } 28 | } 29 | 30 | nav { 31 | float: none; 32 | overflow: hidden; 33 | 34 | @include breakpoint($breakpoint-small) { 35 | float: left; 36 | } 37 | 38 | li { 39 | float: left; 40 | list-style: none; 41 | 42 | a { 43 | float: inherit; 44 | } 45 | } 46 | 47 | img { 48 | position: relative; 49 | top: 4px; 50 | } 51 | 52 | a { 53 | margin-right: 15px; 54 | color: $color-bali-hai; 55 | 56 | &:hover { 57 | color: $color-rock-blue; 58 | } 59 | } 60 | 61 | .active a { 62 | color: $color-oxford-blue; 63 | background: $color-lynch; 64 | border-radius: $border-radius; 65 | } 66 | } 67 | 68 | .btn { 69 | display: none; 70 | float: right; 71 | padding: 0 16px; 72 | margin-left: 18px; 73 | border-radius: $border-radius; 74 | background: $color-bright-gray; 75 | color: $color-slate-gray; 76 | 77 | @include breakpoint($breakpoint-small) { 78 | display: initial; 79 | } 80 | 81 | &:hover { 82 | color: $color-light-regent-gray; 83 | background: $color-licorice; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /anchor/libraries/json.php: -------------------------------------------------------------------------------- 1 | Something went wrong, please notify the owner of the site"; 49 | } else { 50 | Errors::exception($e); 51 | } 52 | 53 | Errors::log($e); 54 | die(); 55 | } 56 | 57 | /** 58 | * Import defined routes 59 | */ 60 | if (is_admin()) { 61 | // Set posts per page for admin 62 | Config::set('admin.posts_per_page', 6); 63 | 64 | require APP . 'routes/admin' . EXT; 65 | require APP . 'routes/categories' . EXT; 66 | require APP . 'routes/comments' . EXT; 67 | require APP . 'routes/fields' . EXT; 68 | require APP . 'routes/menu' . EXT; 69 | require APP . 'routes/metadata' . EXT; 70 | require APP . 'routes/pages' . EXT; 71 | require APP . 'routes/panel' . EXT; 72 | require APP . 'routes/plugins' . EXT; 73 | require APP . 'routes/posts' . EXT; 74 | require APP . 'routes/users' . EXT; 75 | require APP . 'routes/variables' . EXT; 76 | require APP . 'routes/pagetypes' . EXT; 77 | } else { 78 | require APP . 'routes/site' . EXT; 79 | } 80 | -------------------------------------------------------------------------------- /anchor/views/categories/add.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

5 |
6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 |
14 |

15 | 16 | 'label-title']); ?> 17 | 18 |

19 |

20 | 21 | 'label-slug']); ?> 22 | 23 |

24 |

25 | 26 | 'label-description']); ?> 27 | 28 |

29 | 30 |

31 | 32 | 33 |

34 | 35 |
36 | 37 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /anchor/libraries/registry.php: -------------------------------------------------------------------------------- 1 | {$key} : $obj); 30 | } 31 | 32 | return $default; 33 | } 34 | 35 | /** 36 | * Retrieves a key from the registry 37 | * 38 | * @param string $key name of the key to retrieve 39 | * @param null $default (optional) fallback value for missing keys 40 | * 41 | * @return mixed|null key value if found, fallback if given, or null otherwise 42 | */ 43 | public static function get($key, $default = null) 44 | { 45 | if (isset(static::$data[$key])) { 46 | return static::$data[$key]; 47 | } 48 | 49 | return $default; 50 | } 51 | 52 | /** 53 | * Sets a value to the registry 54 | * 55 | * @param string $key name of the key to set 56 | * @param mixed $value value to set 57 | * 58 | * @return void 59 | */ 60 | public static function set($key, $value) 61 | { 62 | static::$data[$key] = $value; 63 | } 64 | 65 | /** 66 | * Checks whether the registry has a key 67 | * 68 | * @param string $key name of the key to check for 69 | * 70 | * @return bool 71 | */ 72 | public static function has($key) 73 | { 74 | return isset(static::$data[$key]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /install/views/start.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Hello. Willkommen. Bonjour. Croeso.

6 | 7 |

If you were looking for a truly lightweight blogging experience, you’ve 8 | found the right place. Simply fill in the details below, and you’ll have your 9 | new blog set up in no time.

10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |

18 | 22 | 28 |

29 | 30 |

31 | 35 | 47 |

48 |
49 | 50 |
51 | 52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /anchor/views/intro.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <?php echo __('global.welcome_to_anchor'); ?> 7 | 8 | 60 | 61 | 62 |
63 | Anchor logo 64 |

65 | 66 |
67 | 68 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /anchor/functions/users.php: -------------------------------------------------------------------------------- 1 | id; 27 | } 28 | } 29 | 30 | /** 31 | * Retrieves the username of the authenticated user 32 | * 33 | * @return string|null 34 | * @throws \Exception 35 | */ 36 | function user_authed_name() 37 | { 38 | if ($user = Auth::user()) { 39 | return $user->username; 40 | } 41 | } 42 | 43 | /** 44 | * Retrieves the email address of the authenticated user 45 | * 46 | * @return string|null 47 | * @throws \Exception 48 | */ 49 | function user_authed_email() 50 | { 51 | if ($user = Auth::user()) { 52 | return $user->email; 53 | } 54 | } 55 | 56 | /** 57 | * Retrieves the role of the authenticated user 58 | * 59 | * @return string|null 60 | * @throws \Exception 61 | */ 62 | function user_authed_role() 63 | { 64 | if ($user = Auth::user()) { 65 | return $user->role; 66 | } 67 | } 68 | 69 | /** 70 | * Retrieves the real name of the authenticated user 71 | * 72 | * @return string|null 73 | * @throws \Exception 74 | */ 75 | function user_authed_real_name() 76 | { 77 | if ($user = Auth::user()) { 78 | return $user->real_name; 79 | } 80 | } 81 | 82 | /** 83 | * Retrieves the user model object 84 | * 85 | * @return \stdClass 86 | * @throws \Exception 87 | */ 88 | function user_object() 89 | { 90 | return Auth::user(); 91 | } 92 | 93 | /** 94 | * Checks whether the current user is an admin 95 | * 96 | * @return bool 97 | * @throws \Exception 98 | */ 99 | function user_is_admin() 100 | { 101 | return user_authed_role() == 'administrator'; 102 | } 103 | -------------------------------------------------------------------------------- /system/autoloader.php: -------------------------------------------------------------------------------- 1 | data = $array; 36 | } 37 | 38 | /** 39 | * Get a server array item 40 | * 41 | * @param string $key key to retrieve from the server data 42 | * @param mixed|null $fallback fallback value for missing keys 43 | * 44 | * @return mixed|null key if found, fallback if given or null 45 | */ 46 | public function get($key, $fallback = null) 47 | { 48 | if (array_key_exists($key, $this->data)) { 49 | return $this->data[$key]; 50 | } 51 | 52 | return $fallback; 53 | } 54 | 55 | /** 56 | * Set a server array item 57 | * 58 | * @param string $key name of the key to set 59 | * @param string $value value to set for the key 60 | * 61 | * @return void 62 | */ 63 | public function set($key, $value) 64 | { 65 | $this->data[$key] = $value; 66 | } 67 | 68 | /** 69 | * Remove a server array item 70 | * 71 | * @param string $key name of the key to erase 72 | * 73 | * @return void 74 | */ 75 | public function erase($key) 76 | { 77 | if ($this->has($key)) { 78 | unset($this->data[$key]); 79 | } 80 | } 81 | 82 | /** 83 | * Check if a server array item exists 84 | * 85 | * @param string $key name of the key to check 86 | * 87 | * @return bool whether the server data item exists 88 | */ 89 | public function has($key) 90 | { 91 | return array_key_exists($key, $this->data); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /themes/default/functions.php: -------------------------------------------------------------------------------- 1 | 4) ? 'th' : (($test < 4) ? ($test < 3) ? ($test < 2) ? ($test < 1) ? 'th' : 'st' : 'nd' : 'rd' : 'th')); 17 | return $number . $ext; 18 | } 19 | 20 | function count_words($str) 21 | { 22 | return count(preg_split('/\s+/', strip_tags($str), null, PREG_SPLIT_NO_EMPTY)); 23 | } 24 | 25 | function pluralise($amount, $str, $alt = '') 26 | { 27 | return intval($amount) === 1 ? $str : $str . ($alt !== '' ? $alt : 's'); 28 | } 29 | 30 | function relative_time($date) 31 | { 32 | if (is_numeric($date)) { 33 | $date = '@' . $date; 34 | } 35 | 36 | $user_timezone = new DateTimeZone(Config::app('timezone')); 37 | $date = new DateTime($date, $user_timezone); 38 | 39 | // get current date in user timezone 40 | $now = new DateTime('now', $user_timezone); 41 | 42 | $elapsed = $now->format('U') - $date->format('U'); 43 | 44 | if ($elapsed <= 1) { 45 | return 'Just now'; 46 | } 47 | 48 | $times = array( 49 | 31104000 => 'year', 50 | 2592000 => 'month', 51 | 604800 => 'week', 52 | 86400 => 'day', 53 | 3600 => 'hour', 54 | 60 => 'minute', 55 | 1 => 'second' 56 | ); 57 | 58 | foreach ($times as $seconds => $title) { 59 | $rounded = $elapsed / $seconds; 60 | 61 | if ($rounded > 1) { 62 | $rounded = round($rounded); 63 | return $rounded . ' ' . pluralise($rounded, $title) . ' ago'; 64 | } 65 | } 66 | } 67 | 68 | function twitter_account() 69 | { 70 | return site_meta('twitter', 'idiot'); 71 | } 72 | 73 | function twitter_url() 74 | { 75 | return 'https://twitter.com/' . twitter_account(); 76 | } 77 | 78 | function total_articles() 79 | { 80 | return total_posts(); 81 | } 82 | -------------------------------------------------------------------------------- /anchor/views/comments/edit.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

post)->get(); 6 | echo $commented_post[0]->title; ?>

7 |
8 | 9 |
10 |
11 | 12 | 13 |
14 |

15 | 16 | name), ['id' => 'label-name']); ?> 17 | 18 |

19 | 20 |

21 | 22 | email), ['id' => 'label-email']); ?> 23 | 24 |

25 | 26 |

27 | 28 | text), ['id' => 'label-text']); ?> 29 | 30 |

31 | 32 |

33 | 34 | status), 35 | ['id' => 'label-status']); ?> 36 | 37 |

38 |
39 | 40 | 49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /themes/default/js/main.js: -------------------------------------------------------------------------------- 1 | var Anchor = { 2 | init: function() { 3 | Anchor.slidey = $('.slidey'); 4 | Anchor.keys = []; 5 | 6 | // Uh, bind to the resizing of the window? 7 | $(window).resize(Anchor.bindResize).trigger('resize'); 8 | 9 | // Re-/Set keys 10 | $(window).on('keyup', Anchor.keyup); 11 | $(window).on('keydown', Anchor.keydown); 12 | 13 | // Set up the toggle link 14 | Anchor.linky = $('.linky').on('click', Anchor.toggleSlidey); 15 | 16 | // Hide the thingymabob 17 | setTimeout(function() { 18 | // Set up the slidey panel 19 | Anchor.hideSlidey(); 20 | 21 | $('body').addClass('js-enabled'); 22 | }, 10); 23 | 24 | // Listen for search link 25 | $('a[href="#search"]').click(function() { 26 | if(!Anchor.linky.hasClass('active')) { 27 | return Anchor.toggleSlidey.call(Anchor.linky); 28 | } 29 | }); 30 | }, 31 | 32 | keyup: function(event) { 33 | Anchor.keys[event.keyCode] = false; 34 | }, 35 | 36 | keydown: function(event) { 37 | Anchor.keys[event.keyCode] = true; 38 | 39 | // ctrl + shift + f => show Slidey and/or focus search bar 40 | if(Anchor.keys[17] && Anchor.keys[16] && Anchor.keys[70]) { 41 | event.preventDefault(); 42 | 43 | Anchor.showSlidey.call(Anchor.linky); 44 | $('input[type="search"]').focus(); 45 | } 46 | 47 | // esc => hide Slidey 48 | if(Anchor.keys[27]) { 49 | event.preventDefault(); 50 | 51 | Anchor.hideSlidey(); 52 | $('input[type="search"]').blur(); 53 | } 54 | }, 55 | 56 | hideSlidey: function() { 57 | Anchor.slidey.css('margin-top', this._slideyHeight); 58 | Anchor.linky && Anchor.linky.removeClass('active'); 59 | 60 | return this; 61 | }, 62 | 63 | showSlidey: function() { 64 | Anchor.slidey.css('margin-top', 0); 65 | Anchor.linky && Anchor.linky.addClass('active'); 66 | 67 | return this; 68 | }, 69 | 70 | toggleSlidey: function() { 71 | var self = Anchor; 72 | var me = $(this); 73 | 74 | me.toggleClass('active'); 75 | self.slidey.css('margin-top', me.hasClass('active') ? 0 : self._slideyHeight); 76 | 77 | return false; 78 | }, 79 | 80 | bindResize: function() { 81 | Anchor._slideyHeight = -(Anchor.slidey.height() + 1); 82 | Anchor.hideSlidey(); 83 | } 84 | }; 85 | 86 | // And bind loading 87 | $(Anchor.init); 88 | -------------------------------------------------------------------------------- /themes/default/article.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 |
6 | 7 |
8 | 9 |
10 | 11 |

This article is my oldest. It is words long, and it’s got for now.

12 |
13 |
14 | 15 | 16 |
17 | 18 |
    19 | 20 |
  • 21 |
    22 |

    23 | 24 | 25 |
    26 | 27 |
    28 | 29 | 30 |
    31 |
  • 32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 |

40 | 41 | 42 |

43 | 44 | 48 | 49 |

50 | 51 | 52 |

53 | 54 |

55 | 56 |

57 |
58 | 59 |
60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /anchor/views/categories/edit.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

title); ?>

5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |

13 | 14 | title), ['id' => 'label-title']); ?> 15 | 16 |

17 |

18 | 19 | slug), ['id' => 'label-slug']); ?> 20 | 21 |

22 |

23 | 24 | description), 25 | ['id' => 'label-description']); ?> 26 | 27 |

28 | 29 |

30 | 31 | 32 |

33 | 34 |
35 | 36 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | global beforeAll, 5 | afterAll, 6 | test, 7 | describe, 8 | expect 9 | */ 10 | 11 | const config = { 12 | 13 | // base URL where AnchorCMS runs 14 | baseUrl: 'http://localhost', 15 | 16 | // strict testing mode: If this is not a debugging run, we'll fail on anything unexpected. 17 | // otherwise, we'll output error messages but continue running. 18 | strict: !process.env.DEBUG 19 | }; 20 | 21 | const puppeteer = require( 'puppeteer' ); 22 | const faker = require( 'faker' ); 23 | 24 | let browser, 25 | page; 26 | 27 | /** 28 | * Setup: will open a Chrome instance anc make the page ready 29 | */ 30 | beforeAll( async () => { 31 | 32 | /** 33 | * Launches the Chrome instance, disabling the sandbox and memory restrictions 34 | * as recommended by the Puppeteer dev team 35 | * 36 | * @type {Puppeteer.Browser} 37 | */ 38 | browser = await puppeteer.launch( { 39 | args: [ 40 | '--no-sandbox', 41 | '--disable-setuid-sandbox', 42 | '--disable-dev-shm-usage' 43 | ] 44 | } ); 45 | /** 46 | * Opens a new page and stores a reference 47 | * 48 | * @type {Page} 49 | */ 50 | page = await browser.newPage(); 51 | 52 | try { 53 | await page.goto( config.baseUrl ); 54 | } catch ( error ) { 55 | console.error( `Could not connect to local web server: ${error.message}` ); 56 | 57 | if ( config.strict ) { 58 | console.error( `Failing tests prematurely` ); 59 | 60 | return process.exit( 3 ); 61 | } 62 | } 63 | } ); 64 | 65 | /** 66 | * Teardown: will close the page and the browser 67 | */ 68 | afterAll( async () => { 69 | await page.close(); 70 | await browser.close(); 71 | } ); 72 | 73 | describe( 'Installation', () => { 74 | test( 'User can click on link to start the installer', async () => { 75 | await page.goto( config.baseUrl ); 76 | await page.waitForSelector( '[href="/install/index.php"]' ); 77 | await page.click( '[href="/install/index.php"]' ); 78 | await page.waitForNavigation(); 79 | await page.waitForSelector( '[action="/install/index.php?route=/start"]' ); 80 | }, 10000 ); 81 | } ); 82 | -------------------------------------------------------------------------------- /anchor/libraries/language.php: -------------------------------------------------------------------------------- 1 | 1) { 33 | $file = array_shift($parts); 34 | $line = array_shift($parts); 35 | } 36 | 37 | if (count($parts) == 1) { 38 | $file = 'global'; 39 | $line = array_shift($parts); 40 | } 41 | 42 | if ( ! isset(static::$lines[$file])) { 43 | static::load($file); 44 | } 45 | 46 | if (isset(static::$lines[$file][$line])) { 47 | $text = static::$lines[$file][$line]; 48 | } elseif ($default) { 49 | $text = $default; 50 | } else { 51 | $text = $key; 52 | } 53 | 54 | if (count($args)) { 55 | return call_user_func_array('sprintf', array_merge([$text], $args)); 56 | } 57 | 58 | return $text; 59 | } 60 | 61 | /** 62 | * Loads a translation file 63 | * 64 | * @param string $file translation file filesystem name 65 | * 66 | * @return void 67 | */ 68 | private static function load($file) 69 | { 70 | if (is_readable($lang = static::path($file))) { 71 | 72 | /** @noinspection PhpIncludeInspection */ 73 | static::$lines[$file] = require $lang; 74 | } 75 | } 76 | 77 | /** 78 | * Resolves the path to a translation file 79 | * 80 | * @param string $file filename to resolve 81 | * 82 | * @return string resolved file path 83 | */ 84 | private static function path($file) 85 | { 86 | $language = Config::app('language', 'en_GB'); 87 | 88 | return APP . 'language/' . $language . '/' . $file . '.php'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /anchor/language/en_GB/users.php: -------------------------------------------------------------------------------- 1 | 'Users', 6 | 7 | 'create_user' => 'Create a new user', 8 | 'add_user' => 'Add a new user', 9 | 'editing_user' => 'Editing %s’s Profile', 10 | 'remembered' => 'I know my password', 11 | 'forgotten_password' => 'Forgotten your password?', 12 | 13 | // roles 14 | 'administrator' => 'Admin', 15 | 'administrator_explain' => '', 16 | 17 | 'editor' => 'Editor', 18 | 'editor_explain' => '', 19 | 20 | 'user' => 'User', 21 | 'user_explain' => '', 22 | 23 | // form fields 24 | 'real_name' => 'Real Name', 25 | 'real_name_explain' => '', 26 | 27 | 'bio' => 'Biography', 28 | 'bio_explain' => '', 29 | 30 | 'status' => 'Status', 31 | 'status_explain' => '', 32 | 33 | 'role' => 'Role', 34 | 'role_explain' => '', 35 | 36 | 'username' => 'Username', 37 | 'username_explain' => '', 38 | 'username_missing' => 'Please enter a username, must be at least %s characters', 39 | 40 | 'password' => 'Password', 41 | 'password_explain' => '', 42 | 'password_too_short' => 'Password must be at least %s characters', 43 | 44 | 'new_password' => 'New Password', 45 | 46 | 'email' => 'Email', 47 | 'email_explain' => '', 48 | 'email_missing' => 'Please enter a valid email address', 49 | 'email_not_found' => 'Profile not found.', 50 | 51 | // messages 52 | 'updated' => 'User profile updated.', 53 | 'created' => 'User profile created.', 54 | 'deleted' => 'User profile deleted.', 55 | 'delete_error' => 'You cannot delete your own profile', 56 | 'login_error' => 'Username or password is wrong.', 57 | 'logout_notice' => 'You are now logged out.', 58 | 'recovery_sent' => 'We have sent you an email to confirm your password change.', 59 | 'recovery_expired' => 'Password recovery token has expired, please try again.', 60 | 'password_reset' => 'Your new password has been set. Go and login now!', 61 | 62 | // password recovery email 63 | 'recovery_subject' => 'Password Reset', 64 | 'recovery_message' => 'You have requested to reset your password.' . 65 | 'To continue follow the link below.' . PHP_EOL . '%s', 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /system/database/connectors/mysql.php: -------------------------------------------------------------------------------- 1 | pdo = new PDO($dns, $username, $password); 81 | $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 82 | } catch (PDOException $e) { 83 | throw new ErrorException($e->getMessage()); 84 | } 85 | } 86 | 87 | /** 88 | * Return the pdo instance 89 | * 90 | * @return object|\PDO PDO object 91 | */ 92 | public function instance() 93 | { 94 | return $this->pdo; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /anchor/migrations/213_add_updated_fields_to_tables.php: -------------------------------------------------------------------------------- 1 | has_table($posts)) { 10 | if (!$this->has_table_column($posts, 'updated')) { 11 | $sql = 'ALTER TABLE `' . $posts . '` '; 12 | $sql .= 'ADD COLUMN `updated` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `created`'; 13 | DB::ask($sql); 14 | } 15 | } 16 | 17 | $pages = Base::table('pages'); 18 | 19 | if ($this->has_table($pages)) { 20 | if (!$this->has_table_column($pages, 'updated')) { 21 | $sql = 'ALTER TABLE `' . $pages . '` '; 22 | $sql .= 'ADD COLUMN `updated` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'; 23 | DB::ask($sql); 24 | } 25 | } 26 | 27 | $users = Base::table('users'); 28 | 29 | if ($this->has_table($users)) { 30 | if (!$this->has_table_column($users, 'updated')) { 31 | $sql = 'ALTER TABLE `' . $users . '` '; 32 | $sql .= 'ADD COLUMN `updated` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'; 33 | DB::ask($sql); 34 | } 35 | } 36 | } 37 | 38 | public function down() 39 | { 40 | $posts = Base::table('posts'); 41 | 42 | if ($this->has_table($posts)) { 43 | if ($this->has_table_column($posts, 'updated')) { 44 | $sql = 'ALTER TABLE `' . $posts . '` '; 45 | $sql .= 'DROP COLUMN `updated`'; 46 | DB::ask($sql); 47 | } 48 | } 49 | 50 | $pages = Base::table('pages'); 51 | 52 | if ($this->has_table($pages)) { 53 | if ($this->has_table_column($pages, 'updated')) { 54 | $sql = 'ALTER TABLE `' . $pages . '` '; 55 | $sql .= 'DROP COLUMN `updated`'; 56 | DB::ask($sql); 57 | } 58 | } 59 | 60 | $users = Base::table('users'); 61 | 62 | if ($this->has_table($users)) { 63 | if ($this->has_table_column($users, 'updated')) { 64 | $sql = 'ALTER TABLE `' . $users . '` '; 65 | $sql .= 'DROP COLUMN `updated`'; 66 | DB::ask($sql); 67 | } 68 | } 69 | } 70 | } --------------------------------------------------------------------------------