├── phpstan-baseline.neon
├── .prettierignore
├── www
└── admin
│ ├── images
│ └── attachment.png
│ └── styles
│ ├── building-block-order.css
│ └── building-block-admin-view.css
├── sql
├── fixtures
│ ├── BuildingBlockImageSet.sql
│ ├── BuildingBlockAttachmentSet.sql
│ ├── BuildingBlockMediaSet.sql
│ ├── BuildingBlockMediaEncoding.sql
│ └── BuildingBlockImageDimension.sql
└── tables
│ └── Block.sql
├── phpstan.dist.neon
├── prettier.config.js
├── .gitignore
├── phpcs.xml
├── .editorconfig
├── package.json
├── Building
├── admin
│ └── components
│ │ └── Block
│ │ ├── Edit.php
│ │ ├── XHTMLEdit.php
│ │ ├── xhtml-edit.xml
│ │ ├── attachment-edit.xml
│ │ ├── video-edit.xml
│ │ ├── image-edit.xml
│ │ ├── Delete.php
│ │ ├── Order.php
│ │ ├── VideoEdit.php
│ │ ├── ImageEdit.php
│ │ └── AttachmentEdit.php
├── dataobjects
│ ├── BuildingBlockWrapper.php
│ └── BuildingBlock.php
├── views
│ ├── BuildingBlockAudioView.php
│ ├── BuildingBlockXHTMLView.php
│ ├── BuildingBlockView.php
│ ├── BuildingBlockCompositeView.php
│ ├── BuildingBlockVideoView.php
│ ├── BuildingBlockImageView.php
│ ├── BuildingBlockAttachmentView.php
│ └── BuildingBlockAdminCompositeView.php
├── BuildingBlockViewFactory.php
└── Building.php
├── pnpm-lock.yaml
├── README.md
├── .github
├── pull_request_template.md
└── workflows
│ └── pull-requests.yml
├── Jenkinsfile
├── LICENSE
├── composer.json
└── .php-cs-fixer.php
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /composer.lock
2 | /pnpm-lock.yaml
3 |
--------------------------------------------------------------------------------
/www/admin/images/attachment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cviebrock/Building/master/www/admin/images/attachment.png
--------------------------------------------------------------------------------
/sql/fixtures/BuildingBlockImageSet.sql:
--------------------------------------------------------------------------------
1 | insert into ImageSet (
2 | shortname,
3 | use_cdn,
4 | obfuscate_filename
5 | ) values (
6 | 'block',
7 | true,
8 | false
9 | );
10 |
--------------------------------------------------------------------------------
/phpstan.dist.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | phpVersion: 80200
6 | level: 1
7 | paths:
8 | - Building
9 | editorUrl: '%%file%%:%%line%%'
10 | editorUrlTitle: '%%file%%:%%line%%'
11 |
--------------------------------------------------------------------------------
/sql/fixtures/BuildingBlockAttachmentSet.sql:
--------------------------------------------------------------------------------
1 | insert into AttachmentSet (
2 | title,
3 | shortname,
4 | use_cdn,
5 | obfuscate_filename
6 | ) values (
7 | 'Building Block Files',
8 | 'block',
9 | true,
10 | false
11 | );
12 |
--------------------------------------------------------------------------------
/sql/fixtures/BuildingBlockMediaSet.sql:
--------------------------------------------------------------------------------
1 | insert into MediaSet (
2 | shortname,
3 | use_cdn,
4 | obfuscate_filename,
5 | private,
6 | skin
7 | ) values (
8 | 'block-video',
9 | true,
10 | false,
11 | false,
12 | null
13 | );
14 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @see https://prettier.io/docs/en/configuration.html
3 | * @type {import("prettier").Config}
4 | */
5 | const config = {
6 | singleQuote: true,
7 | tabWidth: 2,
8 | trailingComma: 'none'
9 | };
10 |
11 | export default config;
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | vendor/
5 | node_modules/
6 |
7 | # misc
8 | .DS_Store
9 | .env
10 | .idea/
11 | npm-debug.log*
12 | .php-cs-fixer.cache
13 | *.swp
14 |
15 | # dependency lock file
16 | composer.lock
17 |
18 | # overrides for local tooling
19 | /phpstan.neon
20 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ./Building
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/sql/tables/Block.sql:
--------------------------------------------------------------------------------
1 | create table Block (
2 | id serial,
3 |
4 | createdate timestamp not null,
5 | modified_date timestamp not null default LOCALTIMESTAMP(0),
6 |
7 | displayorder integer not null default 0,
8 | attachment integer null references Attachment(id) on delete set null,
9 | media integer null references Media(id) on delete set null,
10 | image integer null references Image(id) on delete set null,
11 | bodytext text null,
12 |
13 | primary key (id)
14 | );
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.php]
16 | indent_size = 4
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
21 | [Jenkinsfile]
22 | indent_size = 4
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@silverorange/building",
3 | "private": true,
4 | "engines": {
5 | "node": "^22.14"
6 | },
7 | "type": "module",
8 | "scripts": {
9 | "prettier": "prettier --check .",
10 | "prettier:write": "prettier --write ."
11 | },
12 | "devDependencies": {
13 | "prettier": "^3.5.3"
14 | },
15 | "packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
16 | }
17 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/Edit.php:
--------------------------------------------------------------------------------
1 | =14'}
20 | hasBin: true
21 |
22 | snapshots:
23 |
24 | prettier@3.6.2: {}
25 |
--------------------------------------------------------------------------------
/Building/dataobjects/BuildingBlockWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class = SwatDBClassMap::get(BuildingBlock::class);
17 | $this->index_field = 'id';
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building
2 |
3 | Building is a basic CMS Framework based on simple block objects for
4 | [silverorange/site](https://github.com/silverorange/site).
5 |
6 | Building is responsible for the following data objects:
7 |
8 | - Block
9 |
10 | Building also provides a series of view objects for displaying different
11 | block types found in `Building/views`.
12 |
13 | ## Installation
14 |
15 | Make sure the silverorange composer repository is added to the `composer.json`
16 | for the project and then run:
17 |
18 | ```sh
19 | composer require silverorange/building
20 | ```
21 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockAudioView.php:
--------------------------------------------------------------------------------
1 | media instanceof SiteAudioMedia) {
15 | throw new InvalidArgumentException(
16 | sprintf(
17 | 'The view %s can only display blocks with audio.',
18 | get_class($this)
19 | )
20 | );
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/XHTMLEdit.php:
--------------------------------------------------------------------------------
1 | navbar->popEntry();
26 |
27 | if ($this->isNew()) {
28 | $this->navbar->createEntry(Building::_('New Text Content'));
29 | } else {
30 | $this->navbar->createEntry(Building::_('Edit Text Content'));
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/xhtml-edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Text Content
7 |
8 |
9 | Text Content
10 | false
11 |
12 | true
13 | 40
14 | 640px
15 |
16 |
17 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/attachment-edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | File
7 |
8 |
9 | File
10 |
11 |
12 |
13 | Title
14 |
15 | 255
16 | true
17 |
18 |
19 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Add a description of new changes, the reason for new changes, and how the new
4 | changes work here.
5 |
6 | # Testing Instructions (optional)
7 |
8 | Add step-by-step instructions for testing the PR, if necessary.
9 |
10 | 1. Check out this PR
11 | 2. …
12 |
13 | # Developer Checklist
14 |
15 | Before requesting review for this PR, make sure the following tasks are
16 | complete:
17 |
18 | - [ ] I added a link to the relevant Shortcut story, if applicable
19 | - [ ] I added testing instructions, if any
20 | - [ ] I made sure existing CI checks pass
21 | - [ ] I checked that all requirements of the ticket are fulfilled
22 |
23 | # Reviewer Checklist
24 |
25 | Before merging this PR, make sure the following tasks are complete:
26 |
27 | - [ ] I made sure there are no active labels that block merge
28 | - [ ] I followed the testing instructions
29 | - [ ] I made sure the CI checks pass
30 | - [ ] I reviewed the file changes on GitHub
31 | - [ ] I checked that all requirements of the ticket (if any) are fulfilled
32 |
--------------------------------------------------------------------------------
/www/admin/styles/building-block-order.css:
--------------------------------------------------------------------------------
1 | .swat-change-order-item {
2 | border-bottom: 1px solid #eee;
3 | padding: 8px 12px;
4 | }
5 |
6 | .building-block-attachment-wrapper {
7 | display: inline-block;
8 | text-decoration: none;
9 | min-height: 48px;
10 | position: relative;
11 | }
12 |
13 | .building-block-attachment-wrapper:after {
14 | content: '';
15 | display: block;
16 | width: 0;
17 | height: 0;
18 | clear: both;
19 | }
20 |
21 | .building-block-attachment-icon {
22 | display: block;
23 | position: absolute;
24 | left: 0;
25 | top: 0;
26 | width: 48px;
27 | height: 48px;
28 | background: url('../images/attachment.png') center center no-repeat
29 | transparent;
30 | }
31 |
32 | .building-block-attachment-title {
33 | display: block;
34 | float: left;
35 | clear: left;
36 | margin-left: 52px;
37 | margin-top: 4px;
38 | margin-bottom: 4px;
39 | }
40 |
41 | .building-block-attachment-details {
42 | display: block;
43 | float: left;
44 | clear: left;
45 | margin-left: 52px;
46 | font-size: 11px;
47 | color: #888;
48 | }
49 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/video-edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Video
7 |
8 |
9 | text/xml
10 |
11 |
12 | Caption
13 |
14 | 255
15 |
16 |
17 |
18 | Description
19 |
20 |
21 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent any
3 | stages {
4 | stage('Install Composer Dependencies') {
5 | steps {
6 | sh 'rm -rf composer.lock vendor/'
7 | sh 'composer install'
8 | }
9 | }
10 |
11 | stage('Install NPM Dependencies') {
12 | environment {
13 | PNPM_CACHE_FOLDER = "${env.WORKSPACE}/pnpm-cache/${env.BUILD_NUMBER}"
14 | }
15 | steps {
16 | sh 'n -d exec engine corepack enable pnpm'
17 | sh 'n -d exec engine pnpm install'
18 | }
19 | }
20 |
21 | stage('Check PHP Coding Style') {
22 | steps {
23 | sh 'composer run phpcs:ci'
24 | }
25 | }
26 |
27 | stage('Check PHP Static Analysis') {
28 | steps {
29 | sh 'composer run phpstan:ci'
30 | }
31 | }
32 |
33 | stage('Check Formatting') {
34 | steps {
35 | sh 'n -d exec engine pnpm prettier'
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2017 silverorange
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/image-edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Image
7 |
8 |
9 | false
10 |
11 |
12 | Image
13 |
14 | image/jpeg
15 | image/png
16 | image/tiff
17 |
18 |
19 |
20 | Caption
21 |
22 | 255
23 |
24 |
25 |
26 | Description
27 |
28 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "silverorange/building",
3 | "description": "Basic CMS framework.",
4 | "type": "library",
5 | "keywords": [
6 | "cms"
7 | ],
8 | "homepage": "https://github.com/silverorange/building",
9 | "license": "MIT",
10 | "authors": [
11 | {
12 | "name": "Michael Gauthier",
13 | "email": "mike@silverorange.com"
14 | }
15 | ],
16 | "repositories": [
17 | {
18 | "type": "composer",
19 | "url": "https://composer.silverorange.com",
20 | "only": [
21 | "silverorange/*"
22 | ]
23 | }
24 | ],
25 | "require": {
26 | "php": ">=8.2",
27 | "ext-mbstring": "*",
28 | "silverorange/admin": "^7.0.3",
29 | "silverorange/site": "^15.3.2",
30 | "silverorange/swat": "^7.9.2"
31 | },
32 | "require-dev": {
33 | "friendsofphp/php-cs-fixer": "3.64.0",
34 | "phpstan/phpstan": "^1.12"
35 | },
36 | "scripts": {
37 | "phpcs": "./vendor/bin/php-cs-fixer check -v",
38 | "phpcs:ci": "./vendor/bin/php-cs-fixer check --config=.php-cs-fixer.php --no-interaction --show-progress=none --diff --using-cache=no -vvv",
39 | "phpcs:write": "./vendor/bin/php-cs-fixer fix -v",
40 | "phpstan": "./vendor/bin/phpstan analyze",
41 | "phpstan:ci": "./vendor/bin/phpstan analyze -vvv --no-progress --memory-limit 2G",
42 | "phpstan:baseline": "./vendor/bin/phpstan analyze --generate-baseline"
43 | },
44 | "autoload": {
45 | "classmap": [
46 | "Building/"
47 | ]
48 | },
49 | "config": {
50 | "sort-packages": true
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Building/dataobjects/BuildingBlock.php:
--------------------------------------------------------------------------------
1 | table = 'Block';
45 |
46 | $this->registerDateProperty('createdate');
47 | $this->registerDateProperty('modified_date');
48 |
49 | $this->registerInternalProperty(
50 | 'attachment',
51 | SwatDBClassMap::get(SiteAttachment::class)
52 | );
53 |
54 | $this->registerInternalProperty(
55 | 'image',
56 | SwatDBClassMap::get(SiteImage::class)
57 | );
58 |
59 | $this->registerInternalProperty(
60 | 'media',
61 | SwatDBClassMap::get(SiteVideoMedia::class)
62 | );
63 |
64 | $this->id_field = 'integer:id';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.github/workflows/pull-requests.yml:
--------------------------------------------------------------------------------
1 | name: Pull Requests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | runner:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Check out repository code
14 | uses: actions/checkout@v4
15 |
16 | # Can maybe be replaced with pnpm/action-setup@v4 in the future
17 | - name: Setup pnpm/corepack
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version-file: 'package.json'
21 | - run: npm i -g --force corepack && corepack enable
22 |
23 | - name: Setup Node
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version-file: 'package.json'
27 | cache: 'pnpm'
28 |
29 | - name: Install dependencies
30 | run: pnpm install --frozen-lockfile
31 |
32 | - name: Setup PHP with tools
33 | uses: silverorange/actions-setup-php@v2
34 | with:
35 | php-version: '8.2'
36 | extensions: gd
37 |
38 | - name: Get composer cache directory
39 | id: composer-cache
40 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
41 |
42 | - name: Cache dependencies
43 | uses: actions/cache@v4
44 | with:
45 | path: ${{ steps.composer-cache.outputs.dir }}
46 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
47 | restore-keys: ${{ runner.os }}-composer-
48 |
49 | - name: Install PHP dependencies
50 | run: 'composer install'
51 |
52 | - name: Run tests
53 | timeout-minutes: 5
54 | run: |
55 | pnpm prettier
56 | composer run phpcs:ci
57 | composer run phpstan:ci
58 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/Delete.php:
--------------------------------------------------------------------------------
1 | getItemList('integer');
18 | $sql = sprintf($sql, $item_list);
19 | $num = SwatDB::exec($this->app->db, $sql);
20 |
21 | if ($num > 0) {
22 | $this->app->messages->add(
23 | new SwatMessage(Building::_('Content has been deleted.'))
24 | );
25 | }
26 | }
27 |
28 | // build phase
29 |
30 | protected function buildInternal()
31 | {
32 | parent::buildInternal();
33 |
34 | $item_list = $this->getItemList('integer');
35 | $blocks = SwatDB::query(
36 | $this->app->db,
37 | sprintf(
38 | 'select * from Block where id in (%s)',
39 | $this->getItemList('integer')
40 | ),
41 | SwatDBClassMap::get(BuildingBlockWrapper::class)
42 | );
43 |
44 | $view = SiteViewFactory::get($this->app, 'building-block');
45 | $view->getImageView()->setImageDimensionShortname('thumb');
46 |
47 | ob_start();
48 |
49 | $header = new SwatHtmlTag('h1');
50 | $header->setContent('Delete the following content?');
51 | $header->display();
52 |
53 | foreach ($blocks as $block) {
54 | $view->display($block);
55 | }
56 |
57 | $message = $this->ui->getWidget('confirmation_message');
58 | $message->content = ob_get_clean();
59 | $message->content_type = 'text/xml';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockXHTMLView.php:
--------------------------------------------------------------------------------
1 | body_summary_length = (int) $length;
19 | }
20 |
21 | protected function define()
22 | {
23 | $this->definePart('body');
24 | }
25 |
26 | protected function displayContent(BuildingBlock $block)
27 | {
28 | $this->displayBody($block);
29 | }
30 |
31 | protected function displayBody(BuildingBlock $block)
32 | {
33 | if ($block->bodytext != '') {
34 | if ($this->getMode('body') === SiteView::MODE_ALL) {
35 | $div = new SwatHtmlTag('div');
36 | $div->class = 'building-block-bodytext';
37 | $div->setContent($block->bodytext, 'text/xml');
38 | $div->display();
39 | } elseif ($this->getMode('body') === SiteView::MODE_SUMMARY) {
40 | $bodytext = SwatString::condense(
41 | $block->bodytext,
42 | $this->body_summary_length
43 | );
44 | $div = new SwatHtmlTag('div');
45 | $div->class = 'building-block-bodytext';
46 | $div->setContent($bodytext, 'text/xml');
47 | $div->display();
48 | }
49 | }
50 | }
51 |
52 | protected function getCSSClassNames()
53 | {
54 | return array_merge(
55 | parent::getCSSClassNames(),
56 | ['building-block-xhtml-view']
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Building/BuildingBlockViewFactory.php:
--------------------------------------------------------------------------------
1 | media instanceof SiteAudioMedia) {
40 | return 'building-block-audio';
41 | }
42 | if ($block->media instanceof SiteVideoMedia) {
43 | return 'building-block-video';
44 | }
45 | if ($block->image instanceof SiteImage) {
46 | return 'building-block-image';
47 | }
48 | if ($block->attachment instanceof SiteAttachment) {
49 | return 'building-block-attachment';
50 | }
51 |
52 | return 'building-block-xhtml';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockView.php:
--------------------------------------------------------------------------------
1 | id = 'block_' . $block->id;
30 | $container->class = implode(' ', $this->getCSSClassNames());
31 | $container->open();
32 |
33 | $this->displayContent($block);
34 |
35 | $container->close();
36 | }
37 |
38 | public function setCSSClassNames(array $class_names)
39 | {
40 | $this->css_classes = array_unique($class_names);
41 | }
42 |
43 | public function addCSSClassName($class_name)
44 | {
45 | $this->css_classes = array_unique(
46 | array_merge(
47 | [$class_name],
48 | $this->css_classes
49 | )
50 | );
51 | }
52 |
53 | public function removeCSSClassName($class_name)
54 | {
55 | $this->css_classes = array_diff(
56 | $this->css_classes,
57 | [$class_name]
58 | );
59 | }
60 |
61 | abstract protected function displayContent(BuildingBlock $block);
62 |
63 | protected function getCSSClassNames()
64 | {
65 | return array_unique(
66 | array_merge(
67 | ['building-block-view'],
68 | $this->css_classes
69 | )
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/sql/fixtures/BuildingBlockMediaEncoding.sql:
--------------------------------------------------------------------------------
1 | insert into MediaEncoding (
2 | media_set,
3 | default_type,
4 | title,
5 | shortname,
6 | default_encoding,
7 | width
8 | ) values (
9 | (select id from MediaSet where shortname = 'block-video'),
10 | (select id from MediaType where mime_type = 'video/mp4'),
11 | '1920-wide (1080p)',
12 | '1920',
13 | true,
14 | 1920
15 | );
16 |
17 | insert into MediaEncoding (
18 | media_set,
19 | default_type,
20 | title,
21 | shortname,
22 | default_encoding,
23 | width
24 | ) values (
25 | (select id from MediaSet where shortname = 'block-video'),
26 | (select id from MediaType where mime_type = 'video/mp4'),
27 | '1280-wide (720p)',
28 | '1280',
29 | true,
30 | 1280
31 | );
32 |
33 | insert into MediaEncoding (
34 | media_set,
35 | default_type,
36 | title,
37 | shortname,
38 | default_encoding,
39 | width
40 | ) values (
41 | (select id from MediaSet where shortname = 'block-video'),
42 | (select id from MediaType where mime_type = 'video/mp4'),
43 | '1080-wide',
44 | '1080',
45 | true,
46 | 1080
47 | );
48 |
49 | insert into MediaEncoding (
50 | media_set,
51 | default_type,
52 | title,
53 | shortname,
54 | default_encoding,
55 | width
56 | ) values (
57 | (select id from MediaSet where shortname = 'block-video'),
58 | (select id from MediaType where mime_type = 'video/mp4'),
59 | '720-wide',
60 | '720',
61 | true,
62 | 720
63 | );
64 |
65 | insert into MediaEncoding (
66 | media_set,
67 | default_type,
68 | title,
69 | shortname,
70 | default_encoding,
71 | width
72 | ) values (
73 | (select id from MediaSet where shortname = 'block-video'),
74 | (select id from MediaType where mime_type = 'video/mp4'),
75 | '480-wide',
76 | '480',
77 | true,
78 | 480
79 | );
80 |
81 | insert into MediaEncoding (
82 | media_set,
83 | default_type,
84 | title,
85 | shortname,
86 | default_encoding,
87 | width
88 | ) values (
89 | (select id from MediaSet where shortname = 'block-video'),
90 | (select id from MediaType where mime_type = 'video/mp4'),
91 | '320-wide (QVGA)',
92 | '320',
93 | true,
94 | 320
95 | );
96 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | in(__DIR__);
9 |
10 | return (new Config())
11 | ->setParallelConfig(ParallelConfigFactory::detect(null, null, 2**18-1))
12 | ->setRules([
13 | '@PhpCsFixer' => true,
14 | '@PHP82Migration' => true,
15 | 'indentation_type' => true,
16 |
17 | // Overrides for (opinionated) @PhpCsFixer and @Symfony rules:
18 |
19 | // Align "=>" in multi-line array definitions, unless a blank line exists between elements
20 | 'binary_operator_spaces' => ['operators' => ['=>' => 'align_single_space_minimal']],
21 |
22 | // Subset of statements that should be proceeded with blank line
23 | 'blank_line_before_statement' => ['statements' => ['case', 'continue', 'declare', 'default', 'return', 'throw', 'try', 'yield', 'yield_from']],
24 |
25 | // Enforce space around concatenation operator
26 | 'concat_space' => ['spacing' => 'one'],
27 |
28 | // Use {} for empty loop bodies
29 | 'empty_loop_body' => ['style' => 'braces'],
30 |
31 | // Don't change any increment/decrement styles
32 | 'increment_style' => false,
33 |
34 | // Forbid multi-line whitespace before the closing semicolon
35 | 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
36 |
37 | // Clean up PHPDocs, but leave @inheritDoc entries alone
38 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => false],
39 |
40 | // Ensure that traits are listed first in classes
41 | // (it would be nice to enforce more, but we'll start simple)
42 | 'ordered_class_elements' => ['order' => ['use_trait']],
43 |
44 | // Ensure that param and return types are sorted consistently, with null at end
45 | 'phpdoc_types_order' => ['sort_algorithm' => 'alpha', 'null_adjustment' => 'always_last'],
46 |
47 | // Yoda style is too weird
48 | 'yoda_style' => false,
49 | ])
50 | ->setIndent(' ')
51 | ->setLineEnding("\n")
52 | ->setFinder($finder);
53 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/Order.php:
--------------------------------------------------------------------------------
1 | ui->getWidget('options');
17 | $options_list->parent->visible = false;
18 | $options_list->value = 'custom';
19 | }
20 |
21 | // process phase
22 |
23 | protected function saveIndex($id, $index)
24 | {
25 | SwatDB::updateColumn(
26 | $this->app->db,
27 | 'Block',
28 | 'integer:displayorder',
29 | $index,
30 | 'integer:id',
31 | [$id]
32 | );
33 | }
34 |
35 | // build phase
36 |
37 | abstract protected function getBlocks();
38 |
39 | protected function loadData()
40 | {
41 | $blocks = $this->getBlocks();
42 |
43 | $order_widget = $this->ui->getWidget('order');
44 | $order_widget->width = '500px';
45 | $order_widget->height = '500px';
46 | $order_widget->parent->title = null;
47 | $view = $this->getView();
48 | foreach ($blocks as $block) {
49 | ob_start();
50 | $view->display($block);
51 | $order_widget->addOption($block->id, ob_get_clean(), 'text/xml');
52 | }
53 | }
54 |
55 | protected function getView()
56 | {
57 | $view = SiteViewFactory::get($this->app, 'building-block');
58 |
59 | // configure views for display in list
60 | $view->getImageView()->setImageDimensionShortname('thumb');
61 | $view->getXHTMLView()->setBodySummaryLength(200);
62 | $view->getXHTMLView()->setPartMode('body', SiteView::MODE_SUMMARY);
63 |
64 | // don't link attachments
65 | $view->getAttachmentView()->setPartMode(
66 | 'content',
67 | SiteView::MODE_ALL,
68 | false
69 | );
70 |
71 | return $view;
72 | }
73 |
74 | // finalize phase
75 |
76 | public function finalize()
77 | {
78 | parent::finalize();
79 | $this->layout->addHtmlHeadEntry(
80 | 'packages/building/admin/styles/building-block-order.css'
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/sql/fixtures/BuildingBlockImageDimension.sql:
--------------------------------------------------------------------------------
1 | insert into ImageDimension (
2 | image_set,
3 | default_type,
4 | shortname,
5 | title,
6 | max_width,
7 | max_height,
8 | crop,
9 | dpi,
10 | quality,
11 | strip,
12 | interlace,
13 | resize_filter,
14 | upscale
15 | ) values (
16 | (select id from ImageSet where shortname = 'block'),
17 | (select id from ImageType where mime_type = 'image/jpeg'),
18 | 'original',
19 | 'Original',
20 | null,
21 | null,
22 | false,
23 | 72,
24 | 85,
25 | true,
26 | false,
27 | null,
28 | false
29 | );
30 |
31 | insert into ImageDimension (
32 | image_set,
33 | default_type,
34 | shortname,
35 | title,
36 | max_width,
37 | max_height,
38 | crop,
39 | dpi,
40 | quality,
41 | strip,
42 | interlace,
43 | resize_filter,
44 | upscale
45 | ) values (
46 | (select id from ImageSet where shortname = 'block'),
47 | (select id from ImageType where mime_type = 'image/jpeg'),
48 | '720',
49 | '720',
50 | 720,
51 | 720,
52 | false,
53 | 72,
54 | 85,
55 | true,
56 | false,
57 | null,
58 | false
59 | );
60 |
61 | insert into ImageDimension (
62 | image_set,
63 | default_type,
64 | shortname,
65 | title,
66 | max_width,
67 | max_height,
68 | crop,
69 | dpi,
70 | quality,
71 | strip,
72 | interlace,
73 | resize_filter,
74 | upscale
75 | ) values (
76 | (select id from ImageSet where shortname = 'block'),
77 | (select id from ImageType where mime_type = 'image/jpeg'),
78 | '360',
79 | '360',
80 | 360,
81 | 360,
82 | false,
83 | 72,
84 | 85,
85 | true,
86 | false,
87 | null,
88 | false
89 | );
90 |
91 | insert into ImageDimension (
92 | image_set,
93 | default_type,
94 | shortname,
95 | title,
96 | max_width,
97 | max_height,
98 | crop,
99 | dpi,
100 | quality,
101 | strip,
102 | interlace,
103 | resize_filter,
104 | upscale
105 | ) values (
106 | (select id from ImageSet where shortname = 'block'),
107 | (select id from ImageType where mime_type = 'image/jpeg'),
108 | 'thumb',
109 | 'Thumbnail',
110 | 100,
111 | 100,
112 | false,
113 | 72,
114 | 85,
115 | true,
116 | false,
117 | null,
118 | false
119 | );
120 |
121 | insert into ImageDimension (
122 | image_set,
123 | default_type,
124 | shortname,
125 | title,
126 | max_width,
127 | max_height,
128 | crop,
129 | dpi,
130 | quality,
131 | strip,
132 | interlace,
133 | resize_filter,
134 | upscale
135 | ) values (
136 | (select id from ImageSet where shortname = 'block'),
137 | (select id from ImageType where mime_type = 'image/jpeg'),
138 | 'icon',
139 | 'Icon',
140 | 96,
141 | 96,
142 | true,
143 | 72,
144 | 85,
145 | true,
146 | false,
147 | null,
148 | false
149 | );
150 |
--------------------------------------------------------------------------------
/www/admin/styles/building-block-admin-view.css:
--------------------------------------------------------------------------------
1 | .building-block-admin-view {
2 | margin: -16px -15px 16px;
3 | padding: 0;
4 | border-bottom: 1px solid #efe8da;
5 | }
6 |
7 | .building-block-admin-view-content {
8 | padding: 16px 15px;
9 | }
10 |
11 | .building-block-admin-links {
12 | display: block;
13 | background: #fff;
14 | padding: 3px 15px;
15 | margin: 0;
16 | border-bottom: 1px solid #eee;
17 | background: #f8f8f8;
18 | }
19 |
20 | .building-block-admin-links a {
21 | border: 0;
22 | -webkit-box-shadow: none;
23 | -moz-box-shadow: none;
24 | box-shadow: none;
25 | background: transparent;
26 | color: #3465a4;
27 | cursor: pointer;
28 | display: inline-block;
29 | vertical-align: middle;
30 | padding: 8px 6px;
31 | text-shadow: none;
32 | border-radius: 0;
33 | }
34 |
35 | .building-block-admin-links a:hover {
36 | text-shadow: none;
37 | background: #e5e5e5;
38 | cursor: pointer;
39 | }
40 |
41 | .building-block-admin-links a:active {
42 | text-shadow: none;
43 | background: #e5e5e5;
44 | position: static;
45 | color: #3465a4;
46 | -webkit-box-shadow: none;
47 | -moz-box-shadow: none;
48 | box-shadow: none;
49 | }
50 |
51 | .building-block-admin-links a:hover .swat-tool-link-title,
52 | .building-block-admin-links a:active .swat-tool-link-title {
53 | text-shadow: none;
54 | text-decoration: underline;
55 | }
56 |
57 | .building-block-admin-summary {
58 | display: inline-block;
59 | vertical-align: middle;
60 | margin: 0 20px 0 0;
61 | color: #6b5d40;
62 | }
63 |
64 | .building-block-attachment-wrapper {
65 | display: inline-block;
66 | text-decoration: none;
67 | min-height: 48px;
68 | position: relative;
69 | padding: 4px 8px 4px 4px;
70 | }
71 | .building-block-attachment-wrapper:hover {
72 | background: #e5e5e5;
73 | }
74 |
75 | .building-block-attachment-wrapper:after {
76 | content: '';
77 | display: block;
78 | width: 0;
79 | height: 0;
80 | clear: both;
81 | }
82 |
83 | .building-block-attachment-icon {
84 | display: block;
85 | position: absolute;
86 | left: 4px;
87 | top: 4px;
88 | width: 48px;
89 | height: 48px;
90 | background: url('../images/attachment.png') center center no-repeat
91 | transparent;
92 | }
93 |
94 | .building-block-attachment-title {
95 | display: block;
96 | float: left;
97 | clear: left;
98 | margin-left: 52px;
99 | margin-bottom: 4px;
100 | text-decoration: underline;
101 | }
102 |
103 | .building-block-attachment-details {
104 | display: block;
105 | float: left;
106 | clear: left;
107 | margin-left: 52px;
108 | font-size: 11px;
109 | color: #888;
110 | }
111 |
112 | .building-block-video-view {
113 | max-width: 500px;
114 | }
115 |
116 | .building-block-video-title,
117 | .building-block-image-title {
118 | display: block;
119 | margin-top: 10px;
120 | font-weight: bold;
121 | }
122 |
123 | .building-block-video-description,
124 | .building-block-image-desciption {
125 | display: block;
126 | margin-top: 10px;
127 | font-size: 11px;
128 | }
129 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockCompositeView.php:
--------------------------------------------------------------------------------
1 | getViewForBlock($block);
30 | $view->display($block);
31 | }
32 |
33 | public function getViewForBlock(BuildingBlock $block)
34 | {
35 | return $this->getViewForType(
36 | BuildingBlockViewFactory::getViewType($block)
37 | );
38 | }
39 |
40 | protected function displayContent(BuildingBlock $block) {}
41 |
42 | // view setters
43 |
44 | public function setAttachmentView(BuildingBlockAttachmentView $view)
45 | {
46 | $this->views['building-block-attachment'] = $view;
47 | }
48 |
49 | public function setAudioView(BuildingBlockAudioView $view)
50 | {
51 | $this->views['building-block-audio'] = $view;
52 | }
53 |
54 | public function setXHTMLView(BuildingBlockXHTMLView $view)
55 | {
56 | $this->views['building-block-xhtml'] = $view;
57 | }
58 |
59 | public function setImageView(BuildingBlockImageView $view)
60 | {
61 | $this->views['building-block-image'] = $view;
62 | }
63 |
64 | public function setVideoView(BuildingBlockVideoView $view)
65 | {
66 | $this->views['building-block-video'] = $view;
67 | }
68 |
69 | // view getters
70 |
71 | public function getAttachmentView()
72 | {
73 | return $this->getViewForType('building-block-attachment');
74 | }
75 |
76 | public function getAudioView()
77 | {
78 | return $this->getViewForType('building-block-audio');
79 | }
80 |
81 | public function getXHTMLView()
82 | {
83 | return $this->getViewForType('building-block-xhtml');
84 | }
85 |
86 | public function getImageView()
87 | {
88 | return $this->getViewForType('building-block-image');
89 | }
90 |
91 | public function getVideoView()
92 | {
93 | return $this->getViewForType('building-block-video');
94 | }
95 |
96 | // helpers
97 |
98 | protected function getViewForType($type)
99 | {
100 | if (!isset($this->views[$type])) {
101 | $this->views[$type] = $this->createCompositeViewForType($type);
102 | }
103 |
104 | return $this->views[$type];
105 | }
106 |
107 | protected function createCompositeViewForType($type)
108 | {
109 | return SiteViewFactory::get($this->app, $type);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockVideoView.php:
--------------------------------------------------------------------------------
1 | definePart('video');
12 | $this->definePart('title');
13 | $this->definePart('description');
14 | }
15 |
16 | protected function displayContent(BuildingBlock $block)
17 | {
18 | if (!$block->media instanceof SiteVideoMedia) {
19 | throw new InvalidArgumentException(
20 | sprintf(
21 | 'The view %s can only display blocks with videos.',
22 | get_class($this)
23 | )
24 | );
25 | }
26 |
27 | $this->displayVideo($block);
28 | $this->displayDetails($block);
29 | }
30 |
31 | protected function displayDetails(BuildingBlock $block)
32 | {
33 | $parts = [];
34 |
35 | ob_start();
36 | $this->displayTitle($block);
37 | $title = ob_get_clean();
38 | if ($title != '') {
39 | $parts[] = $title;
40 | }
41 |
42 | ob_start();
43 | $this->displayDescription($block);
44 | $description = ob_get_clean();
45 | if ($description != '') {
46 | $parts[] = $description;
47 | }
48 |
49 | if (count($parts) > 0) {
50 | $span = new SwatHtmlTag('span');
51 | $span->class = 'building-block-video-details';
52 | $span->open();
53 |
54 | foreach ($parts as $part) {
55 | echo $part;
56 | }
57 |
58 | $span->close();
59 | }
60 | }
61 |
62 | protected function displayVideo(BuildingBlock $block)
63 | {
64 | if ($this->getMode('video') > SiteView::MODE_NONE) {
65 | $block->media->setFileBase('media');
66 | $player = $block->media->getMediaPlayer($this->app);
67 | $player->display();
68 |
69 | $page = $this->app->getPage();
70 | $page->layout->addHtmlHeadEntrySet($player->getHtmlHeadEntrySet());
71 | }
72 | }
73 |
74 | protected function displayTitle(BuildingBlock $block)
75 | {
76 | $title = $block->media->getTitle();
77 | if ($this->getMode('title') > SiteView::MODE_NONE && $title != '') {
78 | $span = new SwatHtmlTag('span');
79 | $span->class = 'building-block-video-title';
80 | $span->setContent($title);
81 | $span->display();
82 | }
83 | }
84 |
85 | protected function displayDescription(BuildingBlock $block)
86 | {
87 | if ($this->getMode('description') > SiteView::MODE_NONE
88 | && $block->media->description != '') {
89 | $span = new SwatHtmlTag('span');
90 | $span->class = 'building-block-video-description';
91 | $span->setContent($block->media->description, 'text/xml');
92 | $span->display();
93 | }
94 | }
95 |
96 | protected function getCSSClassNames()
97 | {
98 | return array_merge(
99 | parent::getCSSClassNames(),
100 | ['building-block-video-view']
101 | );
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockImageView.php:
--------------------------------------------------------------------------------
1 | image_dimension_shortname = $shortname;
17 | }
18 |
19 | protected function define()
20 | {
21 | $this->definePart('image');
22 | $this->definePart('title');
23 | $this->definePart('description');
24 | }
25 |
26 | protected function displayContent(BuildingBlock $block)
27 | {
28 | if (!$block->image instanceof SiteImage) {
29 | throw new InvalidArgumentException(
30 | sprintf(
31 | 'The view %s can only display blocks with images.',
32 | get_class($this)
33 | )
34 | );
35 | }
36 |
37 | $this->displayImage($block);
38 | $this->displayDetails($block);
39 | }
40 |
41 | protected function displayDetails(BuildingBlock $block)
42 | {
43 | $parts = [];
44 |
45 | ob_start();
46 | $this->displayTitle($block);
47 | $title = ob_get_clean();
48 | if ($title != '') {
49 | $parts[] = $title;
50 | }
51 |
52 | ob_start();
53 | $this->displayDescription($block);
54 | $description = ob_get_clean();
55 | if ($description != '') {
56 | $parts[] = $description;
57 | }
58 |
59 | if (count($parts) > 0) {
60 | $span = new SwatHtmlTag('span');
61 | $span->class = 'building-block-image-details';
62 | $span->open();
63 |
64 | foreach ($parts as $part) {
65 | echo $part;
66 | }
67 |
68 | $span->close();
69 | }
70 | }
71 |
72 | protected function displayImage(BuildingBlock $block)
73 | {
74 | if ($this->getMode('image') > SiteView::MODE_NONE) {
75 | $img = $block->image->getImgTag(
76 | $this->image_dimension_shortname
77 | );
78 | $img->display();
79 | }
80 | }
81 |
82 | protected function displayTitle(BuildingBlock $block)
83 | {
84 | $title = $block->image->getTitle();
85 | if ($this->getMode('title') > SiteView::MODE_NONE && $title != '') {
86 | $span = new SwatHtmlTag('span');
87 | $span->class = 'building-block-image-title';
88 | $span->setContent($title);
89 | $span->display();
90 | }
91 | }
92 |
93 | protected function displayDescription(BuildingBlock $block)
94 | {
95 | if ($this->getMode('description') > SiteView::MODE_NONE
96 | && $block->image->description != '') {
97 | $span = new SwatHtmlTag('span');
98 | $span->class = 'building-block-image-description';
99 | $span->setContent($block->image->description, 'text/xml');
100 | $span->display();
101 | }
102 | }
103 |
104 | protected function getCSSClassNames()
105 | {
106 | return array_merge(
107 | parent::getCSSClassNames(),
108 | ['building-block-image-view']
109 | );
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/VideoEdit.php:
--------------------------------------------------------------------------------
1 | media instanceof SiteVideoMedia) {
22 | if ($this->getObject()->media instanceof SiteVideoMedia) {
23 | $this->media = $this->getObject()->media;
24 | } else {
25 | $media_id = $this->app->initVar('media');
26 | if ($media_id === null) {
27 | /** @var SwatForm $form */
28 | $form = $this->ui->getWidget('edit_form');
29 | $media_id = $form->getHiddenField('media');
30 | }
31 |
32 | $this->media = SwatDBClassMap::new(SiteVideoMedia::class);
33 | $this->media->setDatabase($this->app->db);
34 | if (!$this->media->load($media_id)) {
35 | throw new AdminNotFoundException(
36 | sprintf(
37 | 'Media with id “%s” not found.',
38 | $media_id
39 | )
40 | );
41 | }
42 | }
43 | }
44 |
45 | return $this->media;
46 | }
47 |
48 | // init phase
49 |
50 | protected function initObject()
51 | {
52 | parent::initObject();
53 |
54 | $block = $this->getObject();
55 | if (!$this->isNew() && !$block->media instanceof SiteVideoMedia) {
56 | throw new AdminNotFoundException(
57 | 'Can only edit video content.'
58 | );
59 | }
60 | }
61 |
62 | // process phase
63 |
64 | protected function updateObject()
65 | {
66 | parent::updateObject();
67 |
68 | $media = $this->getMedia();
69 | $this->getObject()->media = $media->id;
70 |
71 | $this->assignUiValuesToObject(
72 | $this->getObject()->media,
73 | [
74 | 'title',
75 | 'description',
76 | ]
77 | );
78 | }
79 |
80 | protected function saveObject()
81 | {
82 | parent::saveObject();
83 |
84 | $this->getObject()->media->save();
85 | }
86 |
87 | // build phase
88 |
89 | protected function buildInternal()
90 | {
91 | parent::buildInternal();
92 |
93 | $media = $this->getMedia();
94 | $media->setFileBase('media');
95 |
96 | /** @var SwatForm $form */
97 | $form = $this->ui->getWidget('edit_form');
98 | $form->addHiddenField('media', $media->id);
99 |
100 | $player = $media->getMediaPlayer($this->app);
101 | ob_start();
102 | $player->display();
103 | /** @var SwatContentBlock $ui_player */
104 | $ui_player = $this->ui->getWidget('player');
105 | $ui_player->content = ob_get_clean();
106 | $this->layout->addHtmlHeadEntrySet($player->getHtmlHeadEntrySet());
107 | }
108 |
109 | protected function loadObject()
110 | {
111 | parent::loadObject();
112 |
113 | $this->assignObjectValuesToUi(
114 | $this->getObject()->media,
115 | [
116 | 'title',
117 | 'description',
118 | ]
119 | );
120 | }
121 |
122 | protected function buildNavBar()
123 | {
124 | parent::buildNavBar();
125 |
126 | $this->navbar->popEntry();
127 |
128 | if ($this->isNew()) {
129 | $this->navbar->createEntry(Building::_('New Video Content'));
130 | } else {
131 | $this->navbar->createEntry(Building::_('Edit Video Content'));
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/Building/Building.php:
--------------------------------------------------------------------------------
1 | definePart('content'); // used to set link that wraps content
12 | $this->definePart('icon');
13 | $this->definePart('title');
14 | $this->definePart('filesize');
15 | $this->definePart('mime-type');
16 | }
17 |
18 | protected function displayContent(BuildingBlock $block)
19 | {
20 | if (!$block->attachment instanceof SiteAttachment) {
21 | throw new InvalidArgumentException(
22 | sprintf(
23 | 'The view %s can only display blocks with attachments.',
24 | get_class($this)
25 | )
26 | );
27 | }
28 |
29 | $link = $this->getLink('content');
30 | if ($link === false) {
31 | $wrapper = new SwatHtmlTag('span');
32 | } else {
33 | $wrapper = new SwatHtmlTag('a');
34 | if ($link === true) {
35 | $wrapper->href = $block->attachment->getUri();
36 | } else {
37 | $wrapper->href = $link;
38 | }
39 | }
40 |
41 | $wrapper->class = 'building-block-attachment-wrapper';
42 | $wrapper->open();
43 |
44 | $this->displayIcon($block);
45 |
46 | ob_start();
47 | $this->displayTitle($block);
48 | $title = ob_get_clean();
49 |
50 | ob_start();
51 | $this->displayDetails($block);
52 | $details = ob_get_clean();
53 |
54 | if ($title != '' || $details != '') {
55 | echo '';
56 | echo $title, $details;
57 | echo '';
58 | }
59 |
60 | $wrapper->close();
61 | }
62 |
63 | protected function displayDetails(BuildingBlock $block)
64 | {
65 | $parts = [];
66 |
67 | ob_start();
68 | $this->displayMimeType($block);
69 | $mime_type = ob_get_clean();
70 | if ($mime_type != '') {
71 | $parts[] = $mime_type;
72 | }
73 |
74 | ob_start();
75 | $this->displayFilesize($block);
76 | $filesize = ob_get_clean();
77 | if ($filesize != '') {
78 | $parts[] = $filesize;
79 | }
80 |
81 | if (count($parts) > 0) {
82 | $span = new SwatHtmlTag('span');
83 | $span->class = 'building-block-attachment-details';
84 | $span->setContent(implode(' - ', $parts), 'text/xml');
85 | $span->display();
86 | }
87 | }
88 |
89 | protected function displayIcon(BuildingBlock $block)
90 | {
91 | if ($this->getMode('icon') > SiteView::MODE_NONE) {
92 | $span = new SwatHtmlTag('span');
93 |
94 | $mime_type_class = sprintf(
95 | 'building-block-attachment-icon-%s',
96 | mb_strtolower(
97 | str_replace(
98 | '/',
99 | '-',
100 | $block->attachment->mime_type
101 | )
102 | )
103 | );
104 | $span->class = 'building-block-attachment-icon ' . $mime_type_class;
105 |
106 | $span->setContent('');
107 | $span->display();
108 | }
109 | }
110 |
111 | protected function displayTitle(BuildingBlock $block)
112 | {
113 | if ($this->getMode('title') > SiteView::MODE_NONE) {
114 | $title = $block->attachment->title;
115 | if ($title == '') {
116 | $title = $block->attachment->getFilename();
117 | }
118 |
119 | $span = new SwatHtmlTag('span');
120 | $span->class = 'building-block-attachment-title';
121 | $span->setContent($title);
122 | $span->display();
123 | }
124 | }
125 |
126 | protected function displayMimeType(BuildingBlock $block)
127 | {
128 | if ($this->getMode('mime-type') > SiteView::MODE_NONE) {
129 | $span = new SwatHtmlTag('span');
130 | $span->class = 'building-block-attachment-mime-type';
131 | $span->setContent($block->attachment->getHumanFileType());
132 | $span->display();
133 | }
134 | }
135 |
136 | protected function displayFilesize(BuildingBlock $block)
137 | {
138 | if ($this->getMode('filesize') > SiteView::MODE_NONE) {
139 | $span = new SwatHtmlTag('span');
140 | $span->class = 'building-block-attachment-filesize';
141 | $span->setContent($block->attachment->getFormattedFileSize());
142 | $span->display();
143 | }
144 | }
145 |
146 | protected function getCSSClassNames()
147 | {
148 | return array_merge(
149 | parent::getCSSClassNames(),
150 | ['building-block-attachment-view']
151 | );
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/ImageEdit.php:
--------------------------------------------------------------------------------
1 | image_set instanceof SiteImageSet) {
27 | if ($this->getObject()->image instanceof SiteImage) {
28 | $this->image_set = $this->getObject()->image->image_set;
29 | } else {
30 | $this->image_set = SwatDBClassMap::new(SiteImageSet::class);
31 | $this->image_set->setDatabase($this->app->db);
32 | $shortname = $this->getImageSetShortname();
33 | if (!$this->image_set->loadByShortname($shortname)) {
34 | throw new AdminNotFoundException(
35 | sprintf(
36 | 'Image set with shortname “%s” not found.',
37 | $shortname
38 | )
39 | );
40 | }
41 | }
42 | }
43 |
44 | return $this->image_set;
45 | }
46 |
47 | protected function getNewImageInstance()
48 | {
49 | $image = SwatDBClassMap::new(SiteImage::class);
50 | $image->setDatabase($this->app->db);
51 | $image->image_set = $this->getImageSet();
52 |
53 | return $image;
54 | }
55 |
56 | // init phase
57 |
58 | protected function initInternal()
59 | {
60 | parent::initInternal();
61 |
62 | if ($this->isNew()) {
63 | $this->ui->getWidget('image_upload')->required = true;
64 | }
65 | }
66 |
67 | protected function initObject()
68 | {
69 | parent::initObject();
70 |
71 | $block = $this->getObject();
72 | if (!$this->isNew() && !$block->image instanceof SiteImage) {
73 | throw new AdminNotFoundException(
74 | 'Can only edit image content.'
75 | );
76 | }
77 | }
78 |
79 | // process phase
80 |
81 | protected function updateObject()
82 | {
83 | parent::updateObject();
84 |
85 | $this->processImage();
86 |
87 | if ($this->getObject()->image instanceof SiteImage) {
88 | $this->assignUiValuesToObject(
89 | $this->getObject()->image,
90 | [
91 | 'title',
92 | 'description',
93 | ]
94 | );
95 | }
96 | }
97 |
98 | protected function processImage()
99 | {
100 | $upload = $this->ui->getWidget('image_upload');
101 | if ($upload->isUploaded()) {
102 | $block = $this->getObject();
103 |
104 | $image = $this->getNewImageInstance();
105 | $image->setFileBase('../images');
106 | $image->process($upload->getTempFileName());
107 |
108 | // Delete the old image. Prevents browser/CDN caching.
109 | if (!$this->isNew()) {
110 | $block->image->setFileBase('../images');
111 | $block->image->delete();
112 | }
113 |
114 | $block->image = $image;
115 | }
116 | }
117 |
118 | protected function saveObject()
119 | {
120 | parent::saveObject();
121 |
122 | if ($this->getObject()->image instanceof SiteImage) {
123 | $this->getObject()->image->save();
124 | }
125 | }
126 |
127 | // build phase
128 |
129 | protected function loadObject()
130 | {
131 | parent::loadObject();
132 |
133 | $image = $this->getObject()->image;
134 | $preview = $this->ui->getWidget('image_preview');
135 |
136 | $preview->visible = true;
137 | $preview->image = $image->getUri('thumb', '../');
138 | $preview->width = $image->getWidth('thumb');
139 | $preview->height = $image->getHeight('thumb');
140 | $preview->preview_image = $image->getUri('original', '../');
141 | $preview->preview_width = $image->getWidth('original');
142 | $preview->preview_height = $image->getHeight('original');
143 |
144 | if ($this->getObject()->image instanceof SiteImage) {
145 | $this->assignObjectValuesToUi(
146 | $this->getObject()->image,
147 | [
148 | 'title',
149 | 'description',
150 | ]
151 | );
152 | }
153 | }
154 |
155 | protected function buildNavBar()
156 | {
157 | parent::buildNavBar();
158 |
159 | $this->navbar->popEntry();
160 |
161 | if ($this->isNew()) {
162 | $this->navbar->createEntry(Building::_('New Image Content'));
163 | } else {
164 | $this->navbar->createEntry(Building::_('Edit Image Content'));
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/Building/admin/components/Block/AttachmentEdit.php:
--------------------------------------------------------------------------------
1 | attachment_set instanceof SiteAttachmentSet) {
27 | if ($this->getObject()->attachment instanceof SiteAttachment) {
28 | $this->attachment_set =
29 | $this->getObject()->attachment->attachment_set;
30 | } else {
31 | $this->attachment_set = SwatDBClassMap::new(SiteAttachmentSet::class);
32 | $this->attachment_set->setDatabase($this->app->db);
33 | $shortname = $this->getAttachmentSetShortname();
34 | if (!$this->attachment_set->loadByShortname($shortname)) {
35 | throw new AdminNotFoundException(
36 | sprintf(
37 | 'Attachment set with shortname “%s” not found.',
38 | $shortname
39 | )
40 | );
41 | }
42 | }
43 | }
44 |
45 | return $this->attachment_set;
46 | }
47 |
48 | protected function getNewAttachmentInstance()
49 | {
50 | $attachment = SwatDBClassMap::new(SiteAttachment::class);
51 | $attachment->setDatabase($this->app->db);
52 | $attachment->attachment_set = $this->getAttachmentSet();
53 |
54 | return $attachment;
55 | }
56 |
57 | // init phase
58 |
59 | protected function initInternal()
60 | {
61 | parent::initInternal();
62 |
63 | if ($this->isNew()) {
64 | $this->ui->getWidget('file_upload')->required = true;
65 | }
66 | }
67 |
68 | protected function initObject()
69 | {
70 | parent::initObject();
71 |
72 | $block = $this->getObject();
73 | if (!$this->isNew() && !$block->attachment instanceof SiteAttachment) {
74 | throw new AdminNotFoundException(
75 | 'Can only edit attachment content.'
76 | );
77 | }
78 | }
79 |
80 | // process phase
81 |
82 | protected function updateObject()
83 | {
84 | parent::updateObject();
85 |
86 | $this->processAttachment();
87 |
88 | if ($this->getObject()->attachment instanceof SiteAttachment) {
89 | $this->assignUiValuesToObject(
90 | $this->getObject()->attachment,
91 | [
92 | 'title',
93 | ]
94 | );
95 | }
96 | }
97 |
98 | protected function processAttachment()
99 | {
100 | $upload = $this->ui->getWidget('file_upload');
101 | if ($upload->isUploaded()) {
102 | $block = $this->getObject();
103 |
104 | $attachment = $this->getNewAttachmentInstance();
105 |
106 | $attachment->file_size = $upload->getSize();
107 | $attachment->mime_type = $upload->getMimeType();
108 | $attachment->original_filename = $upload->getFileName();
109 | $attachment->createdate = $this->getCurrentTime();
110 |
111 | $attachment->setFileBase($this->getFileBase());
112 |
113 | $this->assignUiValuesToObject(
114 | $attachment,
115 | [
116 | 'title',
117 | ]
118 | );
119 |
120 | $attachment->process($upload->getTempFileName());
121 |
122 | // Delete the old attachment. Prevents browser/CDN caching.
123 | if (!$this->isNew()) {
124 | $block->attachment->setFileBase($this->getFileBase());
125 | $block->attachment->delete();
126 | }
127 |
128 | $block->attachment = $attachment;
129 | }
130 | }
131 |
132 | protected function saveObject()
133 | {
134 | parent::saveObject();
135 |
136 | if ($this->getObject()->attachment instanceof SiteAttachment) {
137 | $this->getObject()->attachment->save();
138 | }
139 | }
140 |
141 | protected function getFileBase()
142 | {
143 | return '../attachments';
144 | }
145 |
146 | // build phase
147 |
148 | protected function loadObject()
149 | {
150 | parent::loadObject();
151 |
152 | $attachment = $this->getObject()->attachment;
153 |
154 | if ($attachment instanceof SiteAttachment) {
155 | $this->assignObjectValuesToUi(
156 | $attachment,
157 | [
158 | 'title',
159 | ]
160 | );
161 | }
162 | }
163 |
164 | protected function buildNavBar()
165 | {
166 | parent::buildNavBar();
167 |
168 | $this->navbar->popEntry();
169 |
170 | if ($this->isNew()) {
171 | $this->navbar->createEntry(Building::_('New File Content'));
172 | } else {
173 | $this->navbar->createEntry(Building::_('Edit File Content'));
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Building/views/BuildingBlockAdminCompositeView.php:
--------------------------------------------------------------------------------
1 | edit_link = new SwatToolLink();
26 | $this->edit_link->setFromStock('edit');
27 | $this->edit_link->title = Building::_('Edit');
28 | $this->edit_link->classes[] = 'building-block-edit-link';
29 |
30 | $this->delete_link = new SwatToolLink();
31 | $this->delete_link->setFromStock('delete');
32 | $this->delete_link->title = Building::_('Delete');
33 | $this->delete_link->classes[] = 'building-block-delete-link';
34 | }
35 |
36 | public function display($block)
37 | {
38 | ob_start();
39 | parent::display($block);
40 | $composite_view = ob_get_clean();
41 |
42 | if ($composite_view != '') {
43 | echo '
';
44 | $this->displayHeader($block);
45 | echo '
';
46 | echo $composite_view;
47 | echo '
';
48 | echo '
';
49 | }
50 | }
51 |
52 | protected function define()
53 | {
54 | $this->definePart('summary');
55 | $this->definePart('edit-link');
56 | $this->definePart('delete-link');
57 | }
58 |
59 | protected function displayHeader(BuildingBlock $block)
60 | {
61 | $parts = [];
62 |
63 | ob_start();
64 | $this->displaySummary($block);
65 | $summary = ob_get_clean();
66 | if ($summary != '') {
67 | $parts[] = $summary;
68 | }
69 |
70 | ob_start();
71 | $this->displayEditLink($block);
72 | $link = ob_get_clean();
73 | if ($link != '') {
74 | $parts[] = $link;
75 | }
76 |
77 | $type = BuildingBlockViewFactory::getViewType($block);
78 | if ($type === 'building-block-video') {
79 | $a_tag = new SwatHtmlTag('a');
80 | $a_tag->class = 'swat-tool-link swat-tool-link-edit';
81 | $a_tag->setContent(Building::_('Set Poster Frame'));
82 | $a_tag->href = 'Media/PosterFrame?id=' . $block->media->id;
83 | $parts[] = $a_tag;
84 | }
85 |
86 | ob_start();
87 | $this->displayDeleteLink($block);
88 | $link = ob_get_clean();
89 | if ($link != '') {
90 | $parts[] = $link;
91 | }
92 |
93 | if (count($parts) > 0) {
94 | $span = new SwatHtmlTag('span');
95 | $span->class = 'building-block-admin-links';
96 | $span->open();
97 |
98 | foreach ($parts as $part) {
99 | echo $part;
100 | }
101 |
102 | $span->close();
103 | }
104 | }
105 |
106 | protected function displayEditLink(BuildingBlock $block)
107 | {
108 | if ($this->getMode('edit-link') > SiteView::MODE_NONE) {
109 | $link = $this->getLink('edit-link');
110 |
111 | if ($link === false) {
112 | $this->edit_link->sensitive = false;
113 | } else {
114 | if ($link === true) {
115 | $type = BuildingBlockViewFactory::getViewType($block);
116 | switch ($type) {
117 | case 'building-block-audio':
118 | $link = sprintf('Block/AudioEdit?id=%s', $block->id);
119 | break;
120 |
121 | case 'building-block-video':
122 | $link = sprintf('Block/VideoEdit?id=%s', $block->id);
123 | break;
124 |
125 | case 'building-block-image':
126 | $link = sprintf('Block/ImageEdit?id=%s', $block->id);
127 | break;
128 |
129 | case 'building-block-attachment':
130 | $link = sprintf(
131 | 'Block/AttachmentEdit?id=%s',
132 | $block->id
133 | );
134 | break;
135 |
136 | case 'building-block-xhtml':
137 | default:
138 | $link = sprintf('Block/XHTMLEdit?id=%s', $block->id);
139 | break;
140 | }
141 | }
142 | $this->edit_link->link = $link;
143 | $this->edit_link->sensitive = true;
144 | }
145 |
146 | $this->edit_link->display();
147 | }
148 | }
149 |
150 | protected function displayDeleteLink(BuildingBlock $block)
151 | {
152 | if ($this->getMode('delete-link') > SiteView::MODE_NONE) {
153 | $link = $this->getLink('delete-link');
154 |
155 | if ($link === false) {
156 | $this->delete_link->sensitive = false;
157 | } else {
158 | if ($link === true) {
159 | $link = sprintf('Block/Delete?id=%s', $block->id);
160 | }
161 | $this->delete_link->link = $link;
162 | $this->delete_link->sensitive = true;
163 | }
164 |
165 | $this->delete_link->display();
166 | }
167 | }
168 |
169 | protected function displaySummary(BuildingBlock $block)
170 | {
171 | if ($this->getMode('summary') > SiteView::MODE_NONE) {
172 | $type = BuildingBlockViewFactory::getViewType($block);
173 | switch ($type) {
174 | case 'building-block-audio':
175 | $summary = Building::_('Audio Content');
176 | break;
177 |
178 | case 'building-block-video':
179 | $summary = Building::_('Video Content');
180 | break;
181 |
182 | case 'building-block-image':
183 | $summary = Building::_('Image Content');
184 | break;
185 |
186 | case 'building-block-attachment':
187 | $summary = Building::_('Attachment');
188 | break;
189 |
190 | case 'building-block-xhtml':
191 | default:
192 | $summary = Building::_('Text Content');
193 | break;
194 | }
195 |
196 | $header = new SwatHtmlTag('h4');
197 | $header->setContent($summary);
198 | $header->class = 'building-block-admin-summary';
199 | $header->display();
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------