├── po ├── potfiles.in └── Makefile ├── locale ├── en_CA │ └── LC_MESSAGES │ │ └── site.mo ├── en_GB │ └── LC_MESSAGES │ │ └── site.mo └── en_US │ └── LC_MESSAGES │ └── site.mo ├── tests └── amqp │ ├── amqp-test.ini │ ├── amqp-test-server.xml │ ├── amqp-test-client.php │ ├── amqp-test-server.php │ ├── AMQPTestServer.php │ └── AMQPTestClient.php ├── www ├── styles │ ├── site-admin-forgot-password-page.css │ ├── site-account-reset-password-page.css │ ├── site-search-results-page.css │ ├── site-account-login-page.css │ ├── site-contact-page.css │ ├── site-image-cell-renderer.css │ ├── site-jw-player-media-display.css │ └── site-content-slider.css └── admin │ ├── images │ ├── site-comment-status-slider-bg.gif │ ├── site-comment-status-slider-thumb.gif │ ├── site-comment-display-actions-arrow.png │ └── site-comment-display-actions-arrow@2x.png │ └── styles │ ├── site-image-upload.css │ ├── site-account-merge.css │ └── site-comment-status-slider.css ├── sql ├── mysql │ ├── views │ │ ├── EnabledArticleView.sql │ │ └── AdReferrerByPeriodView.sql │ └── functions │ │ ├── convertTZ.sql │ │ ├── convertToUTC.sql │ │ ├── getArticleNavBar.sql │ │ ├── getArticlePathInfo.sql │ │ ├── getArticlePath.sql │ │ └── findArticle.sql ├── pgsql │ ├── tables │ │ ├── ConfigSetting.sql │ │ ├── ImageType.sql │ │ ├── MediaType.sql │ │ ├── AdReferrer.sql │ │ ├── Instance.sql │ │ ├── Image.sql │ │ ├── AttachmentSet.sql │ │ ├── AttachmentCdnQueue.sql │ │ ├── ImageSet.sql │ │ ├── ArticleInstanceBinding.sql │ │ ├── Ad.sql │ │ ├── MediaPlayer.sql │ │ ├── SiteEmailLog.sql │ │ ├── AccountLoginHistory.sql │ │ ├── ApiSignOnToken.sql │ │ ├── Account.sql │ │ ├── ApiCredential.sql │ │ ├── Comment.sql │ │ ├── MediaEncoding.sql │ │ ├── ImageCdnQueue.sql │ │ ├── MediaCdnQueue.sql │ │ ├── InstanceConfigSetting.sql │ │ ├── MediaSet.sql │ │ ├── ContactMessage.sql │ │ ├── MediaEncodingBinding.sql │ │ ├── ImageDimensionBinding.sql │ │ ├── AccountLoginSession.sql │ │ ├── Attachment.sql │ │ ├── ImageDimension.sql │ │ ├── Media.sql │ │ └── Article.sql │ ├── views │ │ ├── ArticleChildCountView.sql │ │ ├── EnabledArticleView.sql │ │ ├── VisibleArticleView.sql │ │ ├── SuspiciousAccountView.sql │ │ └── AdReferrerByPeriodView.sql │ └── functions │ │ ├── convertTZ.sql │ │ ├── convertToUTC.sql │ │ ├── getArticlePathInfo.sql │ │ ├── getArticlePath.sql │ │ ├── getArticleNavBar.sql │ │ └── findArticle.sql ├── mssql │ └── tables │ │ ├── ImageType.sql │ │ ├── Instance.sql │ │ ├── AccountLoginHistory.sql │ │ ├── Image.sql │ │ ├── ImageSet.sql │ │ ├── AccountLoginSession.sql │ │ ├── ImageDimensionBinding.sql │ │ ├── Account.sql │ │ └── ImageDimension.sql └── fixtures │ ├── SiteBotrMediaSet.sql │ ├── SiteImageType.sql │ ├── SiteMediaType.sql │ └── SiteBotrMediaEncoding.sql ├── Site ├── exceptions │ ├── SiteCommentJSONException.php │ ├── SiteMailException.php │ ├── SiteCdnException.php │ ├── SiteAMQPJobException.php │ ├── SiteCookieException.php │ ├── SiteInvalidImageException.php │ ├── SiteCommandLineException.php │ ├── SiteApiSignOnException.php │ ├── SiteInvalidMacException.php │ ├── SitePathTooLongException.php │ ├── SiteInvalidImageDimensionException.php │ ├── SiteNotFoundException.php │ ├── SiteBadRequestException.php │ ├── SiteException.php │ ├── SiteNotAuthorizedException.php │ ├── SitePathInvalidUtf8Exception.php │ ├── SiteInvalidClassException.php │ ├── SiteAMQPJobFailureException.php │ ├── SiteClassNotFoundException.php │ └── SiteInvalidPropertyException.php ├── SiteObject.php ├── templates │ ├── SiteBlankTemplate.php │ ├── SiteFileLoaderTemplate.php │ ├── SiteSMILTemplate.php │ ├── SiteVTTTemplate.php │ ├── SiteAtomTemplate.php │ ├── SiteJSONTemplate.php │ ├── SiteTemplateInterface.php │ ├── SiteAbstractTemplate.php │ ├── SiteXMLRPCServerTemplate.php │ ├── SiteXMLSiteMapTemplate.php │ └── SiteDefaultTemplate.php ├── pages │ ├── account-sessions.xml │ ├── search-results.xml │ ├── search-form.xml │ ├── SiteConnectionClosePage.php │ ├── SiteJSONPage.php │ ├── account-forgot-password.xml │ ├── account-reset-password.xml │ ├── SiteMessageDisplayPageDecorator.php │ ├── SiteXmlSiteMapIndexPage.php │ ├── SiteDBEditPage.php │ ├── contact.xml │ ├── SiteXMLRPCServer.php │ ├── SiteSearchPage.php │ ├── SitePage.php │ ├── SiteXhtmlExceptionPage.php │ ├── account-login.xml │ └── account-change-password.xml ├── SitePrivateDataDeleter.php ├── layouts │ ├── SiteXmlSiteMapLayout.php │ └── SiteXMLRPCServerLayout.php ├── SiteFulltextSearchEngine.php ├── dataobjects │ ├── SiteVideoScrubberImage.php │ ├── SiteInstanceWrapper.php │ ├── SiteAdWrapper.php │ ├── SiteAudioMediaWrapper.php │ ├── SiteImageCdnTaskWrapper.php │ ├── SiteInstanceConfigSettingWrapper.php │ ├── SiteAccountWrapper.php │ ├── SiteCdnTaskWrapper.php │ ├── SiteImageSetWrapper.php │ ├── SiteCommentWrapper.php │ ├── SiteAccountLoginHistoryWrapper.php │ ├── SiteVideoImage.php │ ├── SiteAccountLoginSessionWrapper.php │ ├── SiteApiCredentialWrapper.php │ ├── SiteAttachmentSetWrapper.php │ ├── SiteImageDimensionWrapper.php │ ├── SiteVideoMediaEncodingWrapper.php │ ├── SiteContactMessageWrapper.php │ ├── SiteMediaTypeWrapper.php │ ├── SiteVideoMediaEncodingBinding.php │ ├── SiteMediaEncodingWrapper.php │ ├── SiteVideoMediaEncodingBindingWrapper.php │ ├── SiteVideoMediaSet.php │ ├── SiteVideoMediaEncoding.php │ ├── SiteSimpleMediaWrapper.php │ ├── SiteSimpleVideoMediaWrapper.php │ ├── SiteImageDimensionBindingWrapper.php │ ├── SiteVideoMediaSetWrapper.php │ ├── SiteImageLazyWrapper.php │ ├── SiteAttachmentWrapper.php │ ├── SiteAccountLoginHistory.php │ ├── SiteVideoMediaWrapper.php │ ├── SiteAdReferrer.php │ ├── SiteInstanceConfigSetting.php │ ├── SiteArticleWrapper.php │ ├── SiteAttachmentCdnTaskWrapper.php │ ├── SiteMediaEncodingBindingWrapper.php │ ├── SiteMediaCdnTaskWrapper.php │ ├── SiteMediaSetWrapper.php │ ├── SiteImageType.php │ └── SiteAttachmentSet.php ├── admin │ ├── SiteCommentVisibilityCellRenderer.php │ ├── components │ │ ├── InstanceSetting │ │ │ ├── include │ │ │ │ ├── SiteConfigPage.php │ │ │ │ └── config-page.xml │ │ │ └── index.xml │ │ ├── Account │ │ │ ├── forgot-password.xml │ │ │ ├── edit.xml │ │ │ ├── search.xml │ │ │ ├── details.xml │ │ │ ├── suspicious.xml │ │ │ └── index.xml │ │ ├── Image │ │ │ ├── delete.xml │ │ │ └── upload.xml │ │ ├── Attachment │ │ │ └── upload.xml │ │ ├── Media │ │ │ └── poster-frame.xml │ │ ├── Ad │ │ │ ├── edit.xml │ │ │ ├── Index.php │ │ │ ├── details.xml │ │ │ └── Delete.php │ │ ├── Article │ │ │ └── include │ │ │ │ └── SiteArticleVisibilityCellRenderer.php │ │ └── Comment │ │ │ ├── index.xml │ │ │ └── edit.xml │ └── SiteCommentApprovalPage.php ├── SiteCommentable.php ├── SiteError.php ├── SiteSentryExceptionLogger.php ├── SiteKeywordEntry.php ├── SiteCommentStatus.php ├── SiteXMLRPCServerFactory.php ├── SiteSentryErrorLogger.php ├── SiteSearchCheckboxList.php ├── SiteUnnamedButton.php ├── SiteAmazonCdnUpdater.php ├── SiteSearchForm.php ├── SiteCommandLineConfigModule.php ├── SiteArticlePath.php ├── SiteLayoutData.php ├── SiteFulltextSearchResult.php ├── SiteSearchIndexer.php ├── SiteArticleSearchEngine.php ├── SitePathEntry.php ├── SiteCdnModule.php ├── SiteTimerCheckpoint.php └── SiteVideoMediaLocalMover.php ├── .gitignore ├── phpstan.dist.neon ├── phpcs.xml ├── .editorconfig ├── Jenkinsfile ├── rector.php ├── .github ├── pull_request_template.md └── workflows │ └── pull-requests.yml ├── dependencies └── site.yaml └── .php-cs-fixer.php /po/potfiles.in: -------------------------------------------------------------------------------- 1 | ../Site 2 | -------------------------------------------------------------------------------- /locale/en_CA/LC_MESSAGES/site.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/locale/en_CA/LC_MESSAGES/site.mo -------------------------------------------------------------------------------- /locale/en_GB/LC_MESSAGES/site.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/locale/en_GB/LC_MESSAGES/site.mo -------------------------------------------------------------------------------- /locale/en_US/LC_MESSAGES/site.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/locale/en_US/LC_MESSAGES/site.mo -------------------------------------------------------------------------------- /tests/amqp/amqp-test.ini: -------------------------------------------------------------------------------- 1 | [amqp] 2 | server = 192.168.0.100 3 | default_namespace = site-test 4 | sync_timeout = 5000 5 | -------------------------------------------------------------------------------- /www/styles/site-admin-forgot-password-page.css: -------------------------------------------------------------------------------- 1 | .swat-footer-form-field .swat-button-processing-throbber { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /sql/mysql/views/EnabledArticleView.sql: -------------------------------------------------------------------------------- 1 | create or replace view EnabledArticleView as 2 | select id from Article 3 | where enabled = 1; 4 | -------------------------------------------------------------------------------- /www/admin/images/site-comment-status-slider-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/www/admin/images/site-comment-status-slider-bg.gif -------------------------------------------------------------------------------- /sql/pgsql/tables/ConfigSetting.sql: -------------------------------------------------------------------------------- 1 | create table ConfigSetting ( 2 | name varchar(255) not null, 3 | value varchar(255), 4 | primary key (name) 5 | ); 6 | -------------------------------------------------------------------------------- /www/admin/images/site-comment-status-slider-thumb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/www/admin/images/site-comment-status-slider-thumb.gif -------------------------------------------------------------------------------- /www/admin/images/site-comment-display-actions-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/www/admin/images/site-comment-display-actions-arrow.png -------------------------------------------------------------------------------- /www/admin/images/site-comment-display-actions-arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cviebrock/site/master/www/admin/images/site-comment-display-actions-arrow@2x.png -------------------------------------------------------------------------------- /sql/pgsql/tables/ImageType.sql: -------------------------------------------------------------------------------- 1 | create table ImageType ( 2 | id serial, 3 | extension varchar(10) not null, 4 | mime_type varchar(50) not null, 5 | primary key(id) 6 | ); 7 | -------------------------------------------------------------------------------- /www/admin/styles/site-image-upload.css: -------------------------------------------------------------------------------- 1 | #image_display { 2 | position: relative; 3 | float: right; 4 | margin: 0 0 20px 2em; 5 | clear: after; 6 | border: none; 7 | } 8 | -------------------------------------------------------------------------------- /sql/mysql/functions/convertTZ.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION convertTZ (param_date timestamp, param_time_zone varchar(50)) RETURNS timestamp 2 | RETURN CONVERT_TZ(param_date, 'UTC', param_time_zone); 3 | -------------------------------------------------------------------------------- /sql/pgsql/views/ArticleChildCountView.sql: -------------------------------------------------------------------------------- 1 | create or replace view ArticleChildCountView as 2 | select parent as article, 3 | count(id) as child_count 4 | from Article 5 | group by parent; 6 | -------------------------------------------------------------------------------- /sql/mssql/tables/ImageType.sql: -------------------------------------------------------------------------------- 1 | create table ImageType ( 2 | id int not null identity, 3 | 4 | extension varchar(10) null, 5 | mime_type varchar(50) null, 6 | 7 | primary key(id) 8 | ); 9 | -------------------------------------------------------------------------------- /sql/mysql/functions/convertToUTC.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION convertToUTC (param_date timestamp, param_time_zone varchar(50)) RETURNS timestamp 2 | RETURN CONVERT_TZ(param_date, param_time_zone, 'UTC'); 3 | -------------------------------------------------------------------------------- /www/styles/site-account-reset-password-page.css: -------------------------------------------------------------------------------- 1 | /* Account Reset Password page */ 2 | 3 | .redirect-links li { 4 | margin-bottom: 0.5em; 5 | } 6 | 7 | .redirect-links a { 8 | font-size: 120%; 9 | } 10 | -------------------------------------------------------------------------------- /sql/pgsql/tables/MediaType.sql: -------------------------------------------------------------------------------- 1 | create table MediaType ( 2 | id serial, 3 | 4 | extension varchar(10), 5 | mime_type varchar(50), 6 | alternate_mime_types varchar(200), 7 | 8 | primary key(id) 9 | ); 10 | 11 | -------------------------------------------------------------------------------- /sql/pgsql/tables/AdReferrer.sql: -------------------------------------------------------------------------------- 1 | create table AdReferrer ( 2 | id serial, 3 | createdate timestamp, 4 | http_referer varchar(255), 5 | ad integer not null references Ad(id) on delete set null, 6 | primary key (id) 7 | ); 8 | 9 | -------------------------------------------------------------------------------- /sql/fixtures/SiteBotrMediaSet.sql: -------------------------------------------------------------------------------- 1 | 2 | insert into MediaSet ( 3 | shortname, 4 | use_cdn, 5 | obfuscate_filename, 6 | private, 7 | skin 8 | ) values ( 9 | 'public', 10 | true, 11 | false, 12 | false, 13 | null 14 | ); 15 | -------------------------------------------------------------------------------- /sql/pgsql/tables/Instance.sql: -------------------------------------------------------------------------------- 1 | create table Instance ( 2 | id serial, 3 | shortname varchar(255), 4 | title varchar(255), 5 | primary key (id) 6 | ); 7 | 8 | CREATE INDEX Instance_shortname_index ON Instance(shortname); 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/amqp/amqp-test-server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tests SiteAMQPModule and SiteAMQPApplication sync and async calls. 4 | 1.0 5 | 6 | -------------------------------------------------------------------------------- /Site/exceptions/SiteCommentJSONException.php: -------------------------------------------------------------------------------- 1 | content; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sql/pgsql/tables/MediaPlayer.sql: -------------------------------------------------------------------------------- 1 | create table MediaPlayer ( 2 | id serial, 3 | 4 | media_set integer not null references MediaSet(id) on delete cascade, 5 | shortname varchar(255), 6 | key varchar(50) not null, 7 | width int, 8 | height int, 9 | 10 | primary key (id) 11 | ); 12 | 13 | create index MediaPlayer_shortname_index on MediaPlayer(shortname); 14 | -------------------------------------------------------------------------------- /sql/pgsql/tables/SiteEmailLog.sql: -------------------------------------------------------------------------------- 1 | create table SiteEmailLog ( 2 | id serial, 3 | createdate timestamp, 4 | instance integer default null, 5 | type varchar(255), 6 | attachment_count integer default 0, 7 | attachment_size integer default 0, 8 | to_address varchar(255), 9 | from_address varchar(255), 10 | recipient_type varchar(3), 11 | 12 | primary key (id) 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /sql/pgsql/functions/convertTZ.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION convertTZ (timestamp, varchar(50)) RETURNS timestamp AS $$ 2 | DECLARE 3 | param_date ALIAS FOR $1; 4 | param_time_zone ALIAS FOR $2; 5 | local_date timestamp; 6 | BEGIN 7 | select into local_date ((param_date at time zone 'UTC') at time zone param_time_zone); 8 | RETURN local_date; 9 | END; 10 | $$ LANGUAGE 'plpgsql' IMMUTABLE; 11 | -------------------------------------------------------------------------------- /sql/pgsql/tables/AccountLoginHistory.sql: -------------------------------------------------------------------------------- 1 | create table AccountLoginHistory ( 2 | id serial, 3 | 4 | account integer not null references Account(id) on delete cascade, 5 | login_date timestamp not null, 6 | ip_address varchar(15), 7 | user_agent varchar(255), 8 | 9 | primary key (id) 10 | ); 11 | 12 | create index AccountLoginHistory_account_index on AccountLoginHistory(account); 13 | -------------------------------------------------------------------------------- /sql/pgsql/functions/convertToUTC.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION convertToUTC (timestamp, varchar(50)) RETURNS timestamp AS $$ 2 | DECLARE 3 | param_date ALIAS FOR $1; 4 | param_time_zone ALIAS FOR $2; 5 | local_date timestamp; 6 | BEGIN 7 | select into local_date ((param_date at time zone param_time_zone) at time zone 'UTC'); 8 | RETURN local_date; 9 | END; 10 | $$ LANGUAGE 'plpgsql' IMMUTABLE; 11 | -------------------------------------------------------------------------------- /.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 | .php-cs-fixer.cache 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | *.swp 16 | 17 | # dependency lock file 18 | composer.lock 19 | yarn.lock 20 | 21 | # overrides for local tooling 22 | /phpstan.neon 23 | -------------------------------------------------------------------------------- /Site/templates/SiteSMILTemplate.php: -------------------------------------------------------------------------------- 1 | content; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Site/templates/SiteVTTTemplate.php: -------------------------------------------------------------------------------- 1 | content; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Site/pages/account-sessions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Site/SitePrivateDataDeleter.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ./Site 11 | 12 | */layouts/xhtml/* 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Site/SiteFulltextSearchEngine.php: -------------------------------------------------------------------------------- 1 | image_set_shortname = 'video-scrubber'; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Site/templates/SiteAtomTemplate.php: -------------------------------------------------------------------------------- 1 | content; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sql/pgsql/tables/ApiSignOnToken.sql: -------------------------------------------------------------------------------- 1 | create table ApiSignOnToken ( 2 | id serial, 3 | 4 | ident varchar(255) not null, 5 | token varchar(255) not null, 6 | 7 | api_credential integer not null references ApiCredential(id) on delete cascade, 8 | createdate timestamp not null default LOCALTIMESTAMP, 9 | 10 | primary key (id) 11 | ); 12 | 13 | create index ApiSignOnToken_ident_index on ApiSignOnToken(ident); 14 | create index ApiSignOnToken_token_index on ApiSignOnToken(token); 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 | -------------------------------------------------------------------------------- /sql/pgsql/tables/Account.sql: -------------------------------------------------------------------------------- 1 | create table Account ( 2 | id serial, 3 | instance integer references Instance(id), 4 | fullname varchar(255), 5 | email varchar(255), 6 | password varchar(255), 7 | password_salt varchar(50), 8 | password_tag varchar(255), 9 | createdate timestamp, 10 | last_login timestamp, 11 | delete_date timestamp, 12 | primary key (id) 13 | ); 14 | 15 | CREATE INDEX Account_instance_index ON Account(instance); 16 | CREATE INDEX Account_email_index ON Account(email); 17 | -------------------------------------------------------------------------------- /sql/fixtures/SiteImageType.sql: -------------------------------------------------------------------------------- 1 | insert into ImageType ( 2 | extension, 3 | mime_type 4 | ) values ( 5 | 'jpg', 6 | 'image/jpeg' 7 | ); 8 | 9 | insert into ImageType ( 10 | extension, 11 | mime_type 12 | ) values ( 13 | 'tif', 14 | 'image/tiff' 15 | ); 16 | 17 | insert into ImageType ( 18 | extension, 19 | mime_type 20 | ) values ( 21 | 'png', 22 | 'image/png' 23 | ); 24 | 25 | insert into ImageType ( 26 | extension, 27 | mime_type 28 | ) values ( 29 | 'gif', 30 | 'image/gif' 31 | ); 32 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteInstanceWrapper.php: -------------------------------------------------------------------------------- 1 | index_field = 'id'; 15 | $this->row_wrapper_class = SwatDBClassMap::get(SiteInstance::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sql/pgsql/tables/ApiCredential.sql: -------------------------------------------------------------------------------- 1 | create table ApiCredential ( 2 | id serial, 3 | title varchar(255) not null, 4 | api_key varchar(255), 5 | api_shared_secret varchar(255), 6 | createdate timestamp not null, 7 | 8 | instance integer references Instance(id) on delete set null, 9 | 10 | primary key (id), 11 | unique (api_key, instance) 12 | ); 13 | 14 | create index ApiCredential_api_key_index on ApiCredential(api_key); 15 | create index ApiCredential_instance_index on ApiCredential(instance); 16 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAdWrapper.php: -------------------------------------------------------------------------------- 1 | index_field = 'id'; 17 | $this->row_wrapper_class = SwatDBClassMap::get(SiteAd::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAudioMediaWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteAudioMedia::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sql/mssql/tables/AccountLoginSession.sql: -------------------------------------------------------------------------------- 1 | create table AccountLoginSession ( 2 | id int not null identity, 3 | 4 | account int not null references Account(id) on delete cascade, 5 | tag varchar(255), 6 | session_id varchar(255) not null, 7 | createdate datetime2 not null, 8 | login_date datetime2 not null, 9 | ip_address varchar(15) not null, 10 | user_agent nvarchar(255), 11 | dirty bit not null default 0, 12 | 13 | primary key(id) 14 | ); 15 | 16 | create index AccountLoginSession_tag on AccountLoginSession(tag); 17 | -------------------------------------------------------------------------------- /sql/pgsql/tables/Comment.sql: -------------------------------------------------------------------------------- 1 | create table Comment ( 2 | id serial, 3 | instance int references Instance(id), 4 | fullname varchar(255), 5 | link varchar(255), 6 | email varchar(255), 7 | bodytext text not null, 8 | status integer not null default 0, 9 | spam boolean not null default false, 10 | ip_address varchar(15), 11 | user_agent varchar(255), 12 | createdate timestamp not null, 13 | primary key (id) 14 | ); 15 | 16 | create index Comment_spam_index on Comment(spam); 17 | create index Comment_status_index on Comment(status); 18 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteImageCdnTaskWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteImageCdnTask::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sql/mssql/tables/ImageDimensionBinding.sql: -------------------------------------------------------------------------------- 1 | create table ImageDimensionBinding ( 2 | image int not null references Image(id), 3 | 4 | dimension int not null references ImageDimension(id), 5 | image_type int not null references ImageType(id), 6 | width int not null, 7 | height int not null, 8 | dpi int not null default 72, 9 | filesize int null, 10 | on_cdn bit not null default 0, 11 | 12 | primary key(image, dimension) 13 | ); 14 | 15 | create index ImageDimensionBinding_image_index on ImageDimensionBinding(image); 16 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteInstanceConfigSettingWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 15 | SwatDBClassMap::get(SiteInstanceConfigSetting::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sql/pgsql/tables/MediaEncoding.sql: -------------------------------------------------------------------------------- 1 | create table MediaEncoding ( 2 | id serial, 3 | 4 | media_set integer not null references MediaSet(id) on delete cascade, 5 | default_type integer references MediaType(id) on delete set null, 6 | 7 | title varchar(255), 8 | shortname varchar(255), 9 | 10 | default_encoding boolean not null default true, 11 | 12 | -- SiteBotrMedia Specific Fields 13 | key varchar(50), 14 | width integer, 15 | 16 | primary key(id) 17 | ); 18 | 19 | create index MediaEncoding_shortname_index on MediaEncoding(shortname); 20 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAccountWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteAccount::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteCdnTaskWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteCdnTask::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteImageSetWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteImageSet::class); 17 | $this->index_field = 'id'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteCommentWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteComment::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAccountLoginHistoryWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 17 | SwatDBClassMap::get(SiteAccountLoginHistory::class); 18 | 19 | $this->index_field = 'id'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sql/pgsql/tables/ImageCdnQueue.sql: -------------------------------------------------------------------------------- 1 | create table ImageCdnQueue ( 2 | id serial, 3 | 4 | operation varchar(255) not null, 5 | 6 | -- for copy operations, and delete operations where we're just deleting from 7 | -- the cdn, but not deleting from the database. 8 | image int default null references Image(id) on delete cascade, 9 | dimension int default null references ImageDimension(id) on delete cascade, 10 | override_http_headers text, 11 | 12 | -- for delete operations 13 | file_path varchar(255), 14 | 15 | error_date timestamp, 16 | 17 | primary key(id) 18 | ); 19 | -------------------------------------------------------------------------------- /sql/pgsql/tables/MediaCdnQueue.sql: -------------------------------------------------------------------------------- 1 | create table MediaCdnQueue ( 2 | id serial, 3 | 4 | operation varchar(255) not null, 5 | 6 | -- for copy operations, and delete operations where we're just deleting from 7 | -- the cdn, but not deleting from the database. 8 | media int default null references Media(id) on delete cascade, 9 | encoding int default null references MediaEncoding(id) on delete cascade, 10 | override_http_headers text, 11 | 12 | -- for delete operations 13 | file_path varchar(255), 14 | 15 | error_date timestamp, 16 | 17 | primary key(id) 18 | ); 19 | -------------------------------------------------------------------------------- /sql/pgsql/tables/InstanceConfigSetting.sql: -------------------------------------------------------------------------------- 1 | create table InstanceConfigSetting ( 2 | name varchar(255) not null, 3 | value varchar(1024), 4 | is_default boolean not null default false, 5 | instance integer not null references Instance(id) on delete cascade, 6 | primary key (name, is_default, instance) 7 | ); 8 | 9 | CREATE INDEX InstanceConfigSetting_name_index ON InstanceConfigSetting(name); 10 | CREATE INDEX InstanceConfigSetting_is_default_index ON InstanceConfigSetting(is_default); 11 | CREATE INDEX InstanceConfigSetting_instance_index ON InstanceConfigSetting(instance); 12 | -------------------------------------------------------------------------------- /sql/pgsql/tables/MediaSet.sql: -------------------------------------------------------------------------------- 1 | create table MediaSet ( 2 | id serial, 3 | 4 | instance integer default null references Instance(id) on delete cascade, 5 | shortname varchar(255), 6 | use_cdn boolean not null default false, 7 | obfuscate_filename boolean not null default false, 8 | private boolean not null default false, 9 | 10 | skin varchar(50), 11 | 12 | primary key (id) 13 | ); 14 | 15 | create index MediaSet_instance_index on MediaSet(instance); 16 | create index MediaSet_shortname_index on MediaSet(shortname); 17 | -------------------------------------------------------------------------------- /Site/admin/SiteCommentVisibilityCellRenderer.php: -------------------------------------------------------------------------------- 1 | spam 17 | ? Site::_('Spam') 18 | : SiteComment::getStatusTitle($this->status); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoImage.php: -------------------------------------------------------------------------------- 1 | image_set_shortname = 'videos'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAccountLoginSessionWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get( 17 | SiteAccountLoginSession::class 18 | ); 19 | 20 | $this->index_field = 'id'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteApiCredentialWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteApiCredential::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAttachmentSetWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteAttachmentSet::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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('Check PHP Coding Style') { 12 | steps { 13 | sh 'composer run phpcs:ci' 14 | } 15 | } 16 | 17 | stage('Check PHP Static Analysis') { 18 | steps { 19 | sh 'composer run phpstan:ci' 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteImageDimensionWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteImageDimension::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoMediaEncodingWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 19 | SwatDBClassMap::get(SiteVideoMediaEncoding::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sql/fixtures/SiteMediaType.sql: -------------------------------------------------------------------------------- 1 | insert into MediaType ( 2 | extension, 3 | mime_type, 4 | alternate_mime_types 5 | ) values ( 6 | 'mp4', 7 | 'video/mp4', 8 | null 9 | ); 10 | 11 | insert into MediaType ( 12 | extension, 13 | mime_type, 14 | alternate_mime_types 15 | ) values ( 16 | 'm4a', 17 | 'audio/mp4', 18 | 'audio/aac, audio/x-m4a, audio/MP4A-LATM, audio/mpeg4-generic' 19 | ); 20 | 21 | insert into MediaType ( 22 | extension, 23 | mime_type, 24 | alternate_mime_types 25 | ) values ( 26 | 'mp3', 27 | 'audio/mpeg', 28 | 'audio/mpeg, audio/mp3, audio/MPA, audio/mpa-robust' 29 | ); 30 | -------------------------------------------------------------------------------- /sql/pgsql/tables/ContactMessage.sql: -------------------------------------------------------------------------------- 1 | create table ContactMessage ( 2 | id serial, 3 | 4 | instance integer references Instance(id), 5 | class_name varchar(255) not null, 6 | subject varchar(255) not null, 7 | email varchar(255) not null, 8 | message text not null, 9 | spam boolean not null default false, 10 | ip_address varchar(15), 11 | user_agent varchar(255), 12 | createdate timestamp not null, 13 | sent_date timestamp, 14 | error_date timestamp, 15 | 16 | primary key (id) 17 | ); 18 | 19 | create index ContactMessage_spam_index on ContactMessage(spam); 20 | -------------------------------------------------------------------------------- /www/admin/styles/site-comment-status-slider.css: -------------------------------------------------------------------------------- 1 | .site-comment-status-slider { 2 | position: relative; 3 | background: url(../images/site-comment-status-slider-bg.gif) 5px 0 no-repeat; 4 | height: 50px; 5 | width: 218px; 6 | } 7 | 8 | .site-comment-status-slider-thumb { 9 | position: absolute; 10 | } 11 | 12 | img.site-comment-status-slider-image { 13 | position: relative; 14 | top: 3px; 15 | } 16 | 17 | .site-comment-status-slider-legend { 18 | color: #666; 19 | font-size: 85%; 20 | } 21 | 22 | .site-comment-status-slider-legend-selected { 23 | color: #333; 24 | font-weight: bold; 25 | } 26 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteContactMessageWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteContactMessage::class); 18 | $this->index_field = 'id'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteMediaTypeWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 19 | SwatDBClassMap::get(SiteMediaType::class); 20 | 21 | $this->index_field = 'id'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sql/mssql/tables/Account.sql: -------------------------------------------------------------------------------- 1 | create table Account ( 2 | id int not null identity, 3 | 4 | instance int null references Instance(id), 5 | fullname nvarchar(255) null, 6 | email nvarchar(255) null, 7 | password nvarchar(255) null, 8 | password_salt varchar(50) null, 9 | password_tag varchar(255) null, 10 | createdate datetime2 null, 11 | last_login datetime2 null, 12 | delete_date datetime2 null, 13 | 14 | primary key (id) 15 | ); 16 | 17 | CREATE INDEX Account_instance_index ON Account(instance); 18 | CREATE INDEX Account_email_index ON Account(email); 19 | -------------------------------------------------------------------------------- /sql/pgsql/tables/MediaEncodingBinding.sql: -------------------------------------------------------------------------------- 1 | create table MediaEncodingBinding ( 2 | media integer not null references Media(id) on delete cascade, 3 | media_encoding integer not null references MediaEncoding(id), 4 | media_type integer not null references MediaType(id), 5 | 6 | filesize bigint, 7 | 8 | on_cdn boolean not null default false, 9 | 10 | -- SiteBotrMedia Specific Fields 11 | key varchar(50), 12 | width integer, 13 | height integer, 14 | 15 | primary key(media, media_encoding) 16 | ); 17 | 18 | create index MediaEncodingBinding_media_index on MediaEncodingBinding(media); 19 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoMediaEncodingBinding.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 19 | SwatDBClassMap::get(SiteMediaEncoding::class); 20 | 21 | $this->index_field = 'id'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoMediaEncodingBindingWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 19 | SwatDBClassMap::get(SiteVideoMediaEncodingBinding::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoMediaSet.php: -------------------------------------------------------------------------------- 1 | run(); 27 | -------------------------------------------------------------------------------- /Site/SiteCommentable.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search Results 7 | false 8 | 9 | text/xml 10 | 11 | 12 | 10 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Site/SiteError.php: -------------------------------------------------------------------------------- 1 | registerInternalProperty( 25 | 'media_set', 26 | SwatDBClassMap::get(SiteVideoMediaSet::class) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sql/mssql/tables/ImageDimension.sql: -------------------------------------------------------------------------------- 1 | create table ImageDimension ( 2 | id int not null identity, 3 | 4 | image_set int not null references ImageSet(id), 5 | default_type int null references ImageType(id), 6 | shortname varchar(255) null, 7 | title nvarchar(255) null, 8 | max_width int null, 9 | max_height int null, 10 | crop bit not null default 0, 11 | dpi int not null default 72, 12 | quality int not null default 85, 13 | strip bit not null default 1, 14 | interlace bit not null default 0, 15 | resize_filter varchar(50) null, 16 | 17 | primary key(id) 18 | ); 19 | 20 | CREATE INDEX ImageDimension_shortname_index ON ImageDimension(shortname); 21 | -------------------------------------------------------------------------------- /Site/exceptions/SiteNotFoundException.php: -------------------------------------------------------------------------------- 1 | title = Site::_('Not Found'); 21 | $this->http_status_code = 404; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteSimpleMediaWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 21 | SwatDBClassMap::get(SiteMedia::class); 22 | 23 | $this->index_field = 'id'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Site/exceptions/SiteBadRequestException.php: -------------------------------------------------------------------------------- 1 | title = Site::_('Bad Request'); 21 | $this->http_status_code = 400; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sql/pgsql/tables/ImageDimension.sql: -------------------------------------------------------------------------------- 1 | create table ImageDimension ( 2 | id serial, 3 | image_set integer not null references ImageSet(id) on delete cascade, 4 | default_type integer not null references ImageType(id) on delete cascade, 5 | shortname varchar(255), 6 | title varchar(255), 7 | max_width integer, 8 | max_height integer, 9 | crop boolean not null default false, 10 | dpi integer not null default 72, 11 | quality integer not null default 85, 12 | strip boolean not null default true, 13 | upscale boolean not null default false, 14 | interlace boolean not null default false, 15 | resize_filter varchar(50), 16 | primary key(id) 17 | ); 18 | 19 | CREATE INDEX ImageDimension_shortname_index ON ImageDimension(shortname); 20 | -------------------------------------------------------------------------------- /tests/amqp/amqp-test-server.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | content; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Site/admin/components/InstanceSetting/include/SiteConfigPage.php: -------------------------------------------------------------------------------- 1 | ['title', 'meta_description'], 'date' => ['time_zone']]; 20 | } 21 | 22 | protected function getUiXml() 23 | { 24 | return __DIR__ . '/config-page.xml'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Site/templates/SiteTemplateInterface.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = 21 | SwatDBClassMap::get(SiteVideoMedia::class); 22 | 23 | $this->index_field = 'id'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Site/pages/search-form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Keywords 8 | 9 | 255 10 | 11 | 12 | 13 | 14 | Search 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Site/exceptions/SiteException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 19 | $message .= "\n" . $error->getUserInfo(); 20 | $code = $error->getCode(); 21 | } 22 | 23 | parent::__construct($message, $code); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Site/pages/SiteConnectionClosePage.php: -------------------------------------------------------------------------------- 1 | app, SiteBlankTemplate::class); 18 | } 19 | 20 | public function init() 21 | { 22 | parent::init(); 23 | header('Connection: close'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Site/exceptions/SiteNotAuthorizedException.php: -------------------------------------------------------------------------------- 1 | title = Site::_('Not Authorized'); 21 | $this->http_status_code = 401; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /www/styles/site-contact-page.css: -------------------------------------------------------------------------------- 1 | .swat-frame { 2 | margin: 0; 3 | } 4 | 5 | #contact_form { 6 | float: right; 7 | width: 300px; 8 | margin-left: 20px; 9 | margin-bottom: 1em; 10 | } 11 | 12 | #contact_form .swat-entry, 13 | #contact_form .swat-flydown, 14 | #contact_form .swat-textarea { 15 | width: 98%; 16 | } 17 | 18 | dl#contact_details { 19 | margin: 1em; 20 | } 21 | 22 | dl#contact_details dt { 23 | font-weight: bold; 24 | margin-bottom: 0.5em; 25 | } 26 | 27 | dl#contact_details dd { 28 | margin-left: 1em; 29 | margin-bottom: 1em; 30 | } 31 | 32 | #email_to_field.swat-form-field label, 33 | #email_to_field.swat-form-field .swat-form-field-contents { 34 | display: inline; 35 | } 36 | 37 | #email_to_field.swat-form-field { 38 | margin-bottom: 1em; 39 | } 40 | -------------------------------------------------------------------------------- /Site/SiteSentryExceptionLogger.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Save Changes 11 | 12 | 13 | Restore Defaults 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteImageDimensionBindingWrapper.php: -------------------------------------------------------------------------------- 1 | index_field = 'dimension'; 18 | $this->row_wrapper_class = $this->getImageDimensionBindingClassName(); 19 | } 20 | 21 | protected function getImageDimensionBindingClassName() 22 | { 23 | return SwatDBClassMap::get(SiteImageDimensionBinding::class); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Site/admin/components/InstanceSetting/include/config-page.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 255 8 | 9 | 10 | 11 | Meta Description 12 | 13 | 14 | 15 | Time Zone 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /sql/pgsql/tables/Media.sql: -------------------------------------------------------------------------------- 1 | create table Media ( 2 | id serial, 3 | 4 | path_key varchar(30) not null, 5 | media_set integer not null references MediaSet(id), 6 | image integer references Image(id) on delete set null, 7 | 8 | title varchar(255), 9 | filename varchar(255), 10 | original_filename varchar(255), 11 | 12 | has_hls boolean not null default false, 13 | downloadable boolean not null default false, 14 | duration integer, 15 | description text, 16 | 17 | key varchar(50), -- deprecated 18 | 19 | createdate timestamp, 20 | 21 | -- SiteVideoMedia specific fields 22 | scrubber_image integer references Image(id) on delete set null, 23 | scrubber_image_count integer not null default 0, 24 | 25 | primary key (id) 26 | ); 27 | 28 | create index Media_path_key_index on Media(path_key); 29 | -------------------------------------------------------------------------------- /www/styles/site-image-cell-renderer.css: -------------------------------------------------------------------------------- 1 | .swat-tile-view .swat-tile { 2 | width: 126px; 3 | height: auto; 4 | padding: 0; 5 | margin: 5px; 6 | float: left; 7 | text-align: center; 8 | } 9 | 10 | .swat-tile-view .swat-tile a { 11 | padding: 10px; 12 | display: block; 13 | text-decoration: none; 14 | } 15 | 16 | .swat-tile-view .swat-tile a:hover { 17 | background: #eee; 18 | } 19 | 20 | .swat-tile-view .swat-tile span.title { 21 | display: block; 22 | height: 2em; 23 | padding: 0.2em 0; 24 | } 25 | 26 | .swat-tile-view .swat-tile .site-image-wrapper { 27 | display: table-cell; 28 | vertical-align: middle; 29 | position: relative; 30 | } 31 | 32 | .swat-tile-view .site-image-wrapper img { 33 | display: block; 34 | margin: 0 auto; 35 | padding: 2px; 36 | border: 1px solid #ddd; 37 | background: #ccc; 38 | } 39 | -------------------------------------------------------------------------------- /tests/amqp/AMQPTestServer.php: -------------------------------------------------------------------------------- 1 | getBody() === 'fail-test') { 10 | $this->logger->debug(' => sending test failure' . PHP_EOL); 11 | $job->sendFail('This is a test exception'); 12 | } else { 13 | $this->logger->debug( 14 | ' => reversing "{string}"' . PHP_EOL, 15 | ['string' => $job->getBody()] 16 | ); 17 | $job->sendSuccess(strrev($job->getBody())); 18 | } 19 | sleep(1); 20 | } 21 | 22 | protected function getDefaultModuleList() 23 | { 24 | return ['config' => 'SiteConfigModule']; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoMediaSetWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteVideoMediaSet::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Site/exceptions/SitePathInvalidUtf8Exception.php: -------------------------------------------------------------------------------- 1 | path = $raw_path; 20 | } 21 | 22 | public function getRawPath() 23 | { 24 | return $this->path; 25 | } 26 | 27 | public function getEscapedPath() 28 | { 29 | return SwatString::escapeBinary($this->getRawPath()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Site/admin/SiteCommentApprovalPage.php: -------------------------------------------------------------------------------- 1 | ui->getWidget('delete_button')->title = Site::_('Deny'); 15 | } 16 | 17 | protected function approve() 18 | { 19 | $this->data_object->status = SiteComment::STATUS_PUBLISHED; 20 | $this->data_object->save(); 21 | $this->data_object->postSave($this->app); 22 | } 23 | 24 | protected function delete() 25 | { 26 | $this->data_object->delete(); 27 | $this->data_object->clearCache($this->app); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Site/SiteKeywordEntry.php: -------------------------------------------------------------------------------- 1 | name !== null) { 27 | $tag->name = $this->name; 28 | } 29 | 30 | return $tag; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Site/templates/SiteAbstractTemplate.php: -------------------------------------------------------------------------------- 1 | display($data); 13 | 14 | return ob_get_clean(); 15 | } 16 | 17 | public function getBaseUri() 18 | { 19 | $uri = $_SERVER['HTTP_HOST']; 20 | $is_stage = (mb_strpos($uri, 'berna.silverorange.com') !== false); 21 | $stage_uri = 'https://' . $_SERVER['HTTP_HOST'] . 22 | mb_substr($_SERVER['REQUEST_URI'], 0, mb_strpos($_SERVER['REQUEST_URI'], 'www') + 3); 23 | 24 | return $is_stage 25 | ? $stage_uri 26 | : 'https://www.emrap.org'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sql/pgsql/views/SuspiciousAccountView.sql: -------------------------------------------------------------------------------- 1 | create or replace view SuspiciousAccountView as 2 | 3 | SELECT accountloginhistory.account, 4 | min(accountloginhistory.login_date) AS first_suspicious_login, 5 | max(accountloginhistory.login_date) AS last_suspicious_login, 6 | count(accountloginhistory.id) AS login_count, 7 | count(DISTINCT accountloginhistory.user_agent) AS user_agent_count, 8 | count(DISTINCT accountloginhistory.ip_address) AS ip_address_count 9 | FROM accountloginhistory 10 | 11 | /* In the past week there have been more than 5 logins from more than 5 IPs and more than 5 user-agents */ 12 | WHERE accountloginhistory.login_date > (now() - '7 days'::interval) 13 | GROUP BY accountloginhistory.account 14 | HAVING count(accountloginhistory.id) > 4 15 | and count(DISTINCT accountloginhistory.user_agent) > 4 16 | and count(DISTINCT accountloginhistory.ip_address) > 4 17 | ; 18 | 19 | -------------------------------------------------------------------------------- /Site/templates/SiteXMLRPCServerTemplate.php: -------------------------------------------------------------------------------- 1 | response; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteImageLazyWrapper.php: -------------------------------------------------------------------------------- 1 | lazy_load recordset wrapper option 16 | */ 17 | class SiteImageLazyWrapper extends SiteImageWrapper 18 | { 19 | public function __construct( 20 | ?MDB2_Result_Common $rs = null, 21 | array $options = [] 22 | ) { 23 | $options = array_merge( 24 | $options, 25 | ['lazy_load' => true] 26 | ); 27 | 28 | parent::__construct($rs, $options); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sql/mysql/functions/getArticleNavBar.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns navbar information from an article. 3 | * 4 | * @param_parent INTEGER: the id of the article to search from. 5 | * 6 | * Returns a set of returned_rows ordered correctly to go in the navbar. 7 | * Checking if the parent articles are enabled is left up to sp_article_find. 8 | * If the article is not found, returns an empty recordset. 9 | * 10 | * This procedure uses recursion to output entries in the correct order for 11 | * applications. 12 | */ 13 | 14 | CREATE PROCEDURE getArticleNavBar(param_id INTEGER) 15 | BEGIN 16 | CREATE TEMPORARY TABLE Tree_Ids(displayorder SERIAL, id INTEGER); 17 | CALL getTreeIds(param_id); 18 | 19 | SELECT Article.id, Article.parent, Article.shortname, Article.title 20 | FROM Article 21 | INNER JOIN Tree_Ids ON Article.id = Tree_Ids.id 22 | ORDER BY Tree_Ids.displayorder desc; 23 | 24 | DROP TABLE Tree_Ids; 25 | END; 26 | -------------------------------------------------------------------------------- /Site/SiteCommentStatus.php: -------------------------------------------------------------------------------- 1 | getLayout($source); 15 | $map = $this->getPageMap(); 16 | 17 | if (!isset($map[$source])) { 18 | throw new SiteNotFoundException(); 19 | } 20 | 21 | $class = $map[$source]; 22 | 23 | return $this->instantiatePage($class, $layout); 24 | } 25 | 26 | protected function getPageMap(): array 27 | { 28 | return []; 29 | } 30 | 31 | protected function getLayout($source): SiteXMLRPCServerLayout 32 | { 33 | return new SiteXMLRPCServerLayout($this->app); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Site/SiteSentryErrorLogger.php: -------------------------------------------------------------------------------- 1 | filter($e)) { 24 | \Sentry\captureException( 25 | new ErrorException( 26 | $e->getMessage(), 27 | 0, 28 | $e->getSeverity(), 29 | $e->getFile(), 30 | $e->getLine() 31 | ) 32 | ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Site/templates/SiteXMLSiteMapTemplate.php: -------------------------------------------------------------------------------- 1 | '; 26 | 27 | echo $data->site_map; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sql/mysql/functions/getArticlePathInfo.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns path information for an article. 3 | * 4 | * @param_id INTEGER: the id of the article. 5 | * 6 | * Returns a set of type_article_path_info. The set is ordered from the leaf to the root. 7 | * If the article is not found, an empty record set is returned. 8 | */ 9 | 10 | CREATE PROCEDURE getTreeIds(IN param_id INTEGER) 11 | BEGIN 12 | DECLARE parent_id INTEGER; 13 | INSERT INTO Tree_Ids (id) values (param_id); 14 | 15 | SELECT parent INTO parent_id FROM Article where id = param_id; 16 | 17 | IF parent_id IS NOT NULL THEN 18 | CALL getTreeIds(parent_id); 19 | END IF; 20 | END; 21 | 22 | CREATE PROCEDURE getArticlePathInfo(param_id INTEGER) 23 | BEGIN 24 | CREATE TEMPORARY TABLE Tree_Ids (displayorder SERIAL, id INTEGER); 25 | CALL getTreeIds(param_id); 26 | 27 | SELECT id, parent, shortname, title FROM Article WHERE id IN ( 28 | SELECT id FROM Tree_Ids); 29 | 30 | DROP TABLE Tree_Ids; 31 | END; 32 | -------------------------------------------------------------------------------- /Site/SiteSearchCheckboxList.php: -------------------------------------------------------------------------------- 1 | getForm(); 19 | 20 | SwatOptionControl::process(); 21 | 22 | $this->getCompositeWidget('check_all')->process(); 23 | 24 | $data = &$form->getFormData(); 25 | 26 | $this->processValues(); 27 | } 28 | 29 | protected function getLiTag($option) 30 | { 31 | $tag = parent::getLiTag($option); 32 | 33 | if (in_array($option->value, $this->highlight_values)) { 34 | $tag->class = 'highlight'; 35 | } 36 | 37 | return $tag; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sql/pgsql/tables/Article.sql: -------------------------------------------------------------------------------- 1 | create table Article ( 2 | id serial, 3 | parent int, -- constraint added below 4 | title varchar(255), 5 | html_title varchar(255), 6 | description text, 7 | bodytext text, 8 | createdate timestamp not null default LOCALTIMESTAMP, 9 | modified_date timestamp not null default LOCALTIMESTAMP, 10 | displayorder int not null default 0, 11 | enabled boolean not null default true, 12 | visible boolean not null default true, 13 | searchable boolean not null default true, 14 | shortname varchar(255), 15 | primary key (id) 16 | ); 17 | 18 | ALTER TABLE Article ADD CONSTRAINT Articlefk FOREIGN KEY (parent) REFERENCES Article(id) MATCH FULL on delete cascade; 19 | 20 | CREATE INDEX Article_parent_index ON Article(parent); 21 | CREATE INDEX Article_shortname_index ON Article(shortname); 22 | CREATE INDEX Article_visible_index ON Article(visible); 23 | CREATE INDEX Article_enabled_index ON Article(enabled); 24 | CREATE INDEX Article_searchable_index ON Article(searchable); 25 | 26 | -------------------------------------------------------------------------------- /Site/pages/SiteJSONPage.php: -------------------------------------------------------------------------------- 1 | setLayout( 17 | new SiteLayout( 18 | $this->app, 19 | SiteJSONTemplate::class 20 | ) 21 | ); 22 | } 23 | 24 | // build phase 25 | 26 | public function build() 27 | { 28 | $this->layout->data->content = json_encode($this->getResponse()); 29 | } 30 | 31 | abstract protected function getResponse(); 32 | 33 | protected function getStatus($code = 'ok', $message = '') 34 | { 35 | return ['code' => $code, 'message' => $message]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 13 | __DIR__ . '/po', 14 | __DIR__ . '/Site', 15 | __DIR__ . '/tests', 16 | __DIR__ . '/www', 17 | ]) 18 | // uncomment to reach your current PHP version 19 | ->withPhpSets(php82: true) 20 | ->withRules([ 21 | ]) 22 | ->withSkip([ 23 | ClassPropertyAssignToConstructorPromotionRector::class, 24 | MixedTypeRector::class, 25 | NullToStrictStringFuncCallArgRector::class, 26 | RemoveUnusedVariableInCatchRector::class, 27 | ]) 28 | ->withTypeCoverageLevel(1) 29 | ->withDeadCodeLevel(1); 30 | -------------------------------------------------------------------------------- /Site/SiteUnnamedButton.php: -------------------------------------------------------------------------------- 1 | visible) { 16 | return; 17 | } 18 | 19 | $form = $this->getFirstAncestor('SwatForm'); 20 | $primary = ($form !== null 21 | && $form->getFirstDescendant('SwatButton') === $this); 22 | 23 | $input_tag = new SwatHtmlTag('input'); 24 | $input_tag->type = 'submit'; 25 | $input_tag->id = $this->id; 26 | $input_tag->value = $this->title; 27 | $input_tag->class = $this->getCSSClassString(); 28 | 29 | if ($this->access_key != '') { 30 | $input_tag->accesskey = $this->access_key; 31 | } 32 | 33 | $input_tag->display(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAttachmentWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteAttachment::class); 18 | $this->index_field = 'id'; 19 | } 20 | 21 | public function getBySet($shortname) 22 | { 23 | $wrapper_class = SwatDBClassMap::get(SiteAttachmentWrapper::class); 24 | $attachments = new $wrapper_class(); 25 | 26 | foreach ($this as $attachment) { 27 | if ($attachment->attachment_set->shortname === $shortname) { 28 | $attachments->add($attachment); 29 | } 30 | } 31 | 32 | return $attachments; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sql/mysql/views/AdReferrerByPeriodView.sql: -------------------------------------------------------------------------------- 1 | create or replace view AdReferrerByPeriodView as 2 | 3 | select id as ad, 4 | -- {{{ day 5 | (select count(ad) 6 | from AdReferrer 7 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval 1 day) and AdReferrer.ad = Ad.id 8 | group by ad) 9 | as day, 10 | -- }}} 11 | -- {{{ week 12 | (select count(ad) 13 | from AdReferrer 14 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval 1 week) and AdReferrer.ad = Ad.id 15 | group by ad) 16 | as week, 17 | -- }}} 18 | -- {{{ 2 week 19 | (select count(ad) 20 | from AdReferrer 21 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval 2 week) and AdReferrer.ad = Ad.id 22 | group by ad) 23 | as two_week, 24 | -- }}} 25 | -- {{{ month 26 | (select count(ad) 27 | from AdReferrer 28 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval 1 month) and AdReferrer.ad = Ad.id 29 | group by ad) 30 | as month, 31 | -- }}} 32 | -- {{{ total 33 | (select count(ad) 34 | from AdReferrer 35 | where AdReferrer.ad = Ad.id 36 | group by ad) 37 | as total 38 | -- }}} 39 | from Ad; 40 | -------------------------------------------------------------------------------- /sql/pgsql/views/AdReferrerByPeriodView.sql: -------------------------------------------------------------------------------- 1 | create or replace view AdReferrerByPeriodView as 2 | 3 | select id as ad, 4 | -- {{{ day 5 | (select count(ad) 6 | from AdReferrer 7 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval '1 day') and AdReferrer.ad = Ad.id 8 | group by ad) 9 | as day, 10 | -- }}} 11 | -- {{{ week 12 | (select count(ad) 13 | from AdReferrer 14 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval '1 week') and AdReferrer.ad = Ad.id 15 | group by ad) 16 | as week, 17 | -- }}} 18 | -- {{{ 2 week 19 | (select count(ad) 20 | from AdReferrer 21 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval '2 week') and AdReferrer.ad = Ad.id 22 | group by ad) 23 | as two_week, 24 | -- }}} 25 | -- {{{ month 26 | (select count(ad) 27 | from AdReferrer 28 | where AdReferrer.createdate > (LOCALTIMESTAMP - interval '1 month') and AdReferrer.ad = Ad.id 29 | group by ad) 30 | as month, 31 | -- }}} 32 | -- {{{ total 33 | (select count(ad) 34 | from AdReferrer 35 | where AdReferrer.ad = Ad.id 36 | group by ad) 37 | as total 38 | -- }}} 39 | from Ad; 40 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Site/admin/components/Account/forgot-password.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | false 12 | 13 | 14 | 15 | apply 16 | Send Reset-Password Email 17 | 18 | 19 | cancel 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Site/SiteAmazonCdnUpdater.php: -------------------------------------------------------------------------------- 1 | cdn->bucket = $config->amazon->bucket; 18 | $this->cdn->access_key_id = $config->amazon->access_key_id; 19 | $this->cdn->access_key_secret = $config->amazon->access_key_secret; 20 | 21 | if ($config->amazon->reduced_redundancy) { 22 | $this->cdn->setReducedRedundancy(); 23 | } 24 | } 25 | 26 | protected function getDefaultModuleList() 27 | { 28 | return array_merge( 29 | parent::getDefaultModuleList(), 30 | [ 31 | 'cdn' => SiteAmazonCdnModule::class, 32 | ] 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAccountLoginHistory.php: -------------------------------------------------------------------------------- 1 | table = 'AccountLoginHistory'; 39 | $this->id_field = 'integer:id'; 40 | 41 | $this->registerDateProperty('login_date'); 42 | 43 | $this->registerInternalProperty( 44 | 'account', 45 | SwatDBClassMap::get(SiteAccount::class) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteVideoMediaWrapper.php: -------------------------------------------------------------------------------- 1 | row_wrapper_class = SwatDBClassMap::get(SiteVideoMedia::class); 18 | } 19 | 20 | protected function getMediaSetWrapperClass() 21 | { 22 | return SwatDBClassMap::get(SiteVideoMediaSetWrapper::class); 23 | } 24 | 25 | protected function getMediaEncodingBindingWrapperClass() 26 | { 27 | return SwatDBClassMap::get(SiteVideoMediaEncodingBindingWrapper::class); 28 | } 29 | 30 | protected function getMediaEncodingBindingOrderBy() 31 | { 32 | // order by width with nulls first so that encodings are ordered from 33 | // audio (no width), then from smallest to largest encoding. 34 | return 'media, width asc nulls first'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Site/admin/components/Image/delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | image 12 | thumb 13 | ../ 14 | 15 | 16 | 17 | 18 | 19 | 20 | delete 21 | 22 | 23 | cancel 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /www/styles/site-jw-player-media-display.css: -------------------------------------------------------------------------------- 1 | .video-player-container { 2 | position: relative; 3 | } 4 | 5 | .video-player .video-player-upgrade { 6 | background: rgba(255, 255, 255, 0.5); 7 | border-radius: 10px; 8 | width: 80%; 9 | margin: 0 10%; 10 | text-shadow: none; 11 | font-size: 14px; 12 | color: #333; 13 | } 14 | 15 | .video-player .video-player-upgrade a { 16 | color: #333!important; 17 | } 18 | 19 | /* Fix for apsect-ratio sized flash player. Was broken in Chrome. */ 20 | .video-player object { 21 | top: 0; 22 | left: 0; 23 | } 24 | 25 | .video-player > div { 26 | vertical-align: bottom; 27 | } 28 | 29 | .video-player .overlay { 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | right: 0; 34 | bottom: 0; 35 | z-index: 2; 36 | background: #fff; 37 | border-radius: 0; 38 | } 39 | 40 | .video-player .overlay-content { 41 | position: absolute; 42 | height: auto; 43 | width: auto; 44 | top: 40px; 45 | left: 40px; 46 | bottom: 40px; 47 | right: 40px; 48 | z-index: 3; 49 | text-align: center; 50 | color: #666; 51 | text-shadow: none; 52 | } 53 | 54 | .video-player.no-captions .jw-icon-cc { 55 | display: none; 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/pull-requests.yml: -------------------------------------------------------------------------------- 1 | name: Pull Requests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 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 | - name: Setup PHP with tools 17 | uses: silverorange/actions-setup-php@v2 18 | with: 19 | php-version: '8.2' 20 | extensions: gd 21 | 22 | - name: Get composer cache directory 23 | id: composer-cache 24 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 25 | 26 | - name: Cache dependencies 27 | uses: actions/cache@v4 28 | with: 29 | path: ${{ steps.composer-cache.outputs.dir }} 30 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 31 | restore-keys: ${{ runner.os }}-composer- 32 | 33 | - name: Install PHP dependencies 34 | run: 'composer install' 35 | 36 | - name: Run tests 37 | timeout-minutes: 5 38 | run: | 39 | composer run phpcs:ci 40 | composer run phpstan:ci 41 | -------------------------------------------------------------------------------- /Site/SiteSearchForm.php: -------------------------------------------------------------------------------- 1 | setMethod(SwatForm::METHOD_GET); 21 | $this->requires_id = false; 22 | } 23 | 24 | public function isSubmitted() 25 | { 26 | /* 27 | * Search forms do not output an hidden field to determine if they 28 | * have been submitted. Instead they are always assummed to be 29 | * submitted. 30 | */ 31 | return true; 32 | } 33 | 34 | protected function displayHiddenFields() 35 | { 36 | /* 37 | * Override to not output any hidden fields since search forms use the 38 | * HTTP GET method. 39 | */ 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Site/exceptions/SiteInvalidClassException.php: -------------------------------------------------------------------------------- 1 | object = $object; 29 | } 30 | 31 | /** 32 | * Gets the object that is of the wrong class. 33 | * 34 | * @return mixed the object that is of the wrong class 35 | */ 36 | public function getObject() 37 | { 38 | return $this->object; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Site/admin/components/Attachment/upload.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Title 9 | 10 | true 11 | 255 12 | 13 | 14 | 15 | File 16 | 17 | true 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Site/exceptions/SiteAMQPJobFailureException.php: -------------------------------------------------------------------------------- 1 | raw_body = (string) $raw_body; 29 | } 30 | 31 | /** 32 | * Gets the raw AMQP response body of this exception. 33 | * 34 | * @return string the raw AMQP response body 35 | */ 36 | public function getRawBody() 37 | { 38 | return $this->raw_body; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Site/admin/components/Account/edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Account 7 | 8 | 9 | Full Name 10 | 11 | true 12 | 255 13 | 14 | 15 | 16 | Email 17 | 18 | true 19 | 255 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Site/admin/components/Media/poster-frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Poster Frame 7 | 8 | 9 | text/xml 10 | 11 | 12 | Custom Poster Frame 13 | 14 | true 15 | image/jpeg 16 | image/png 17 | image/tiff 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Site/admin/components/Ad/edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ad 7 | 8 | 9 | Title 10 | 11 | true 12 | 255 13 | 14 | 15 | 16 | Short Name 17 | 18 | true 19 | 255 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAdReferrer.php: -------------------------------------------------------------------------------- 1 | table = 'AdReferrer'; 44 | $this->id_field = 'integer:id'; 45 | $this->registerDateProperty('createdate'); 46 | $this->registerInternalProperty('ad', SiteAd::class); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteInstanceConfigSetting.php: -------------------------------------------------------------------------------- 1 | table = 'InstanceConfigSetting'; 42 | 43 | $this->registerInternalProperty( 44 | 'instance', 45 | SwatDBClassMap::get(SiteInstance::class) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Site/exceptions/SiteClassNotFoundException.php: -------------------------------------------------------------------------------- 1 | class_name = $class_name; 29 | } 30 | 31 | /** 32 | * Gets the name of the class that is not found. 33 | * 34 | * @return string the name of the class that is not found 35 | */ 36 | public function getClassName() 37 | { 38 | return $this->class_name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Site/pages/account-forgot-password.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Forgot your password? No problem. 6 | 7 | 8 | Simply enter your email address and a link to create a new password will be sent to you.

]]>
9 | text/xml 10 |
11 | 12 | Email 13 | 14 | true 15 | 255 16 | 17 | 18 | 19 | 20 | Request a New Password 21 | 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /sql/pgsql/functions/getArticlePathInfo.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns path information for an article. 3 | * 4 | * @param_id INTEGER: the id of the article. 5 | * 6 | * @returned_row type_article_path_info: a row containing id, parent, shortname, title 7 | * 8 | * Returns a set of type_article_path_info. The set is ordered from the leaf to the root. 9 | * If the article is not found, an empty record set is returned. 10 | */ 11 | CREATE TYPE type_article_path_info AS (id INTEGER, parent INTEGER, shortname VARCHAR(255), title VARCHAR(255)); 12 | 13 | CREATE OR REPLACE FUNCTION getArticlePathInfo(INTEGER) RETURNS SETOF type_article_path_info AS $$ 14 | DECLARE 15 | param_id ALIAS FOR $1; 16 | local_id INTEGER; 17 | returned_row type_article_path_info%ROWTYPE; 18 | BEGIN 19 | local_id := param_id; 20 | 21 | WHILE local_id is not null LOOP 22 | BEGIN 23 | SELECT INTO returned_row id, parent, shortname, title 24 | FROM Article 25 | WHERE id = local_id; 26 | 27 | -- return the row 28 | IF FOUND THEN 29 | RETURN NEXT returned_row; 30 | END IF; 31 | 32 | -- move up the tree 33 | local_id := returned_row.parent; 34 | END; 35 | END LOOP; 36 | 37 | RETURN; 38 | END; 39 | $$ LANGUAGE 'plpgsql'; 40 | -------------------------------------------------------------------------------- /sql/mysql/functions/getArticlePath.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns the path string of an article. 3 | * 4 | * @param_parent INTEGER: the id of the category to search from. 5 | * 6 | * Returns a VARCHAR containing the path string for the given category. If the 7 | * category does not exist, NULL is returned. 8 | */ 9 | 10 | CREATE FUNCTION getArticlePath(param_id INTEGER) RETURNS VARCHAR(255) 11 | BEGIN 12 | DECLARE local_parent INTEGER; 13 | DECLARE local_shortname VARCHAR(255); 14 | DECLARE local_path VARCHAR(255); 15 | 16 | SET local_path = NULL; 17 | 18 | -- get current article results 19 | SELECT parent, shortname INTO local_parent, local_shortname 20 | FROM Article 21 | WHERE id = param_id; 22 | 23 | IF FOUND THEN 24 | SET local_path = local_shortname; 25 | END IF; 26 | 27 | -- get parent article results 28 | WHILE local_parent IS NOT NULL DO 29 | 30 | SELECT parent, shortname INTO local_parent, local_shortname 31 | FROM Article 32 | WHERE id = local_parent; 33 | 34 | IF FOUND THEN 35 | IF local_path IS NULL THEN 36 | SET local_path = local_shortname; 37 | ELSE 38 | SET local_path = CONCAT(local_shortname, '/', local_path); 39 | END IF; 40 | END IF; 41 | END WHILE; 42 | 43 | RETURN local_path; 44 | END; 45 | -------------------------------------------------------------------------------- /tests/amqp/AMQPTestClient.php: -------------------------------------------------------------------------------- 1 | initModules(); 10 | $this->parseCommandLineArguments(); 11 | 12 | $strings = ['test', 'abcdefg', 'fail-test']; 13 | 14 | $this->debug("Async test:\n", true); 15 | 16 | foreach ($strings as $string) { 17 | $this->debug($string . "\n"); 18 | $this->amqp->doAsync('strrev', $string); 19 | } 20 | 21 | $this->debug("\n"); 22 | $this->debug("Sync test:\n", true); 23 | 24 | try { 25 | foreach ($strings as $string) { 26 | $this->debug($string . ' => '); 27 | $this->debug($this->amqp->doSync('strrev', $string)); 28 | $this->debug("\n"); 29 | } 30 | } catch (SiteAMQPJobFailureException $e) { 31 | $this->debug("CAUGHT EXPECTED FAIL\n"); 32 | } 33 | 34 | $this->debug("\n"); 35 | $this->debug("done\n", true); 36 | } 37 | 38 | protected function getDefaultModuleList() 39 | { 40 | return ['config' => 'SiteConfigModule', 'amqp' => 'SiteAMQPModule']; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteArticleWrapper.php: -------------------------------------------------------------------------------- 1 | shortname === $shortname) { 28 | $returned_article = $article; 29 | break; 30 | } 31 | } 32 | 33 | return $returned_article; 34 | } 35 | 36 | protected function init() 37 | { 38 | parent::init(); 39 | $this->row_wrapper_class = SwatDBClassMap::get(SiteArticle::class); 40 | $this->index_field = 'id'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAttachmentCdnTaskWrapper.php: -------------------------------------------------------------------------------- 1 | getOption('lazy_load')) { 23 | $this->loadAllSubDataObjects( 24 | 'attachment', 25 | $this->db, 26 | 'select * from Attachment where id in (%s)', 27 | SwatDBClassMap::get(SiteAttachmentWrapper::class) 28 | ); 29 | } 30 | } 31 | 32 | protected function init() 33 | { 34 | parent::init(); 35 | 36 | $this->row_wrapper_class = SwatDBClassMap::get(SiteAttachmentCdnTask::class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sql/pgsql/functions/getArticlePath.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns the path string of an article. 3 | * 4 | * @param_parent INTEGER: the id of the article to search from. 5 | * 6 | * Returns a VARCHAR containing the path string for the given article. If the 7 | * article does not exist, NULL is returned. 8 | */ 9 | CREATE OR REPLACE FUNCTION getArticlePath(INTEGER) RETURNS VARCHAR(255) AS $$ 10 | DECLARE 11 | param_id ALIAS FOR $1; 12 | local_parent INTEGER; 13 | local_shortname VARCHAR(255); 14 | local_path VARCHAR(255); 15 | BEGIN 16 | local_path = NULL; 17 | 18 | -- get current article results 19 | SELECT INTO local_parent, local_shortname parent, shortname 20 | FROM Article 21 | WHERE id = param_id; 22 | 23 | IF FOUND THEN 24 | local_path = local_shortname; 25 | END IF; 26 | 27 | -- get parent article results 28 | WHILE local_parent IS NOT NULL LOOP 29 | BEGIN 30 | 31 | SELECT INTO local_parent, local_shortname parent, shortname 32 | FROM Article 33 | WHERE id = local_parent; 34 | 35 | IF FOUND THEN 36 | IF local_path IS NULL THEN 37 | local_path = local_shortname; 38 | ELSE 39 | local_path = local_shortname || '/' || local_path; 40 | END IF; 41 | END IF; 42 | 43 | END; 44 | END LOOP; 45 | 46 | RETURN local_path; 47 | END; 48 | $$ LANGUAGE 'plpgsql'; 49 | -------------------------------------------------------------------------------- /Site/pages/account-reset-password.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | New Password 10 | 11 | 4 12 | true 13 | 14 | 15 | 16 | Confirm New Password 17 | Password is case-sensitive. 18 | 19 | 20 | 21 | 22 | Update Password 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Site/SiteCommandLineConfigModule.php: -------------------------------------------------------------------------------- 1 | app->database->dsn = $this->database->dsn; 32 | 33 | if ($this->date->time_zone !== null) { 34 | $this->app->default_time_zone = 35 | new DateTimeZone($this->date->time_zone); 36 | } 37 | 38 | $this->app->default_locale = $this->i18n->locale; 39 | 40 | setlocale(LC_ALL, $this->i18n->locale . '.UTF-8'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Site/admin/components/Image/upload.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | false 9 | 10 | 11 | Upload an image to be automatically resized 12 | 13 | Image 14 | 15 | 16 | 17 | 18 | false 19 | or Upload images that have been manually resized 20 | 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteMediaEncodingBindingWrapper.php: -------------------------------------------------------------------------------- 1 | getOption('lazy_load')) { 23 | $this->loadAllSubDataObjects( 24 | 'media_type', 25 | $this->db, 26 | 'select * from MediaType where id in (%s)', 27 | SwatDBClassMap::get(SiteMediaTypeWrapper::class) 28 | ); 29 | } 30 | } 31 | 32 | protected function init() 33 | { 34 | parent::init(); 35 | 36 | $this->row_wrapper_class = 37 | SwatDBClassMap::get(SiteMediaEncodingBinding::class); 38 | 39 | $this->index_field = 'media_encoding'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Site/admin/components/Article/include/SiteArticleVisibilityCellRenderer.php: -------------------------------------------------------------------------------- 1 | enabled) { 23 | $messages[] = Site::_('not enabled'); 24 | } elseif ($this->display_positive_states) { 25 | $messages[] = Site::_('enabled'); 26 | } 27 | 28 | if (!$this->searchable) { 29 | $messages[] = Site::_('not searchable'); 30 | } elseif ($this->display_positive_states) { 31 | $messages[] = Site::_('searchable'); 32 | } 33 | 34 | if (!$this->show_in_menu) { 35 | $messages[] = Site::_('not shown in menu'); 36 | } elseif ($this->display_positive_states) { 37 | $messages[] = Site::_('shown in menu'); 38 | } 39 | 40 | echo implode($this->separator, $messages); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Site/pages/SiteMessageDisplayPageDecorator.php: -------------------------------------------------------------------------------- 1 | message_display = new SwatMessageDisplay(); 21 | } 22 | 23 | // build phase 24 | 25 | public function build() 26 | { 27 | parent::build(); 28 | 29 | foreach ($this->app->messages->getAll() as $message) { 30 | $this->message_display->add($message); 31 | } 32 | 33 | if ($this->message_display->getMessageCount() > 0) { 34 | $this->layout->startCapture('content', true); 35 | $this->message_display->display(); 36 | $this->layout->endCapture(); 37 | } 38 | } 39 | 40 | // finalize phase 41 | 42 | public function finalize() 43 | { 44 | parent::finalize(); 45 | 46 | if ($this->message_display->getMessageCount() > 0) { 47 | $this->layout->addHtmlHeadEntrySet( 48 | $this->message_display->getHtmlHeadEntrySet() 49 | ); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Site/SiteArticlePath.php: -------------------------------------------------------------------------------- 1 | loadFromId($app, $id); 20 | } 21 | } 22 | 23 | /** 24 | * Creates a new path object. 25 | * 26 | * @param int $article_id 27 | */ 28 | public function loadFromId(SiteWebApplication $app, $article_id) 29 | { 30 | foreach ($this->queryPath($app, $article_id) as $row) { 31 | $this->addEntry(new SitePathEntry( 32 | $row->id, 33 | $row->parent, 34 | $row->shortname, 35 | $row->title 36 | )); 37 | } 38 | } 39 | 40 | protected function queryPath($app, $article_id) 41 | { 42 | return SwatDB::executeStoredProc( 43 | $app->db, 44 | 'getArticlePathInfo', 45 | $article_id 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Site/admin/components/Ad/Index.php: -------------------------------------------------------------------------------- 1 | ui->loadFromXML($this->getUiXml()); 17 | } 18 | 19 | protected function getUiXml() 20 | { 21 | return __DIR__ . '/index.xml'; 22 | } 23 | 24 | // process phase 25 | 26 | protected function processActions(SwatView $view, SwatActions $actions) 27 | { 28 | $num = count($view->getSelection()); 29 | 30 | switch ($actions->selected->id) { 31 | case 'delete': 32 | $this->app->replacePage('Ad/Delete'); 33 | $this->app->getPage()->setItems($view->getSelection()); 34 | break; 35 | } 36 | } 37 | 38 | // build phase 39 | 40 | protected function getTableModel(SwatView $view): ?SwatTableModel 41 | { 42 | $sql = sprintf( 43 | 'select * from Ad 44 | order by %s', 45 | $this->getOrderByClause($view, 'createdate desc') 46 | ); 47 | 48 | return SwatDB::query( 49 | $this->app->db, 50 | $sql, 51 | SwatDBClassMap::get(SiteAdWrapper::class) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Site/pages/SiteXmlSiteMapIndexPage.php: -------------------------------------------------------------------------------- 1 | setLayout(new SiteXmlSiteMapLayout($this->app)); 17 | } 18 | 19 | public function build() 20 | { 21 | $this->layout->startCapture('site_map'); 22 | 23 | echo ''; 24 | $this->displaySiteMapIndex(); 25 | echo ''; 26 | 27 | $this->layout->endCapture(); 28 | } 29 | 30 | protected function displayIndex($path, ?SwatDate $last_modified = null) 31 | { 32 | echo "\n"; 33 | 34 | printf( 35 | "%s\n", 36 | htmlspecialchars($this->app->getBaseHref() . $path) 37 | ); 38 | 39 | if ($last_modified !== null) { 40 | printf( 41 | "%s\n", 42 | $last_modified->getISO8601() 43 | ); 44 | } 45 | 46 | echo "\n"; 47 | } 48 | 49 | abstract protected function displaySiteMapIndex(); 50 | } 51 | -------------------------------------------------------------------------------- /Site/SiteLayoutData.php: -------------------------------------------------------------------------------- 1 | display($this); 17 | } 18 | 19 | /** 20 | * @deprecated use the isset() function on this class instead 21 | * 22 | * @param mixed $name 23 | */ 24 | public function exists($name) 25 | { 26 | return isset($this->{$name}); 27 | } 28 | 29 | public function __isset($name) 30 | { 31 | return isset($this->_properties[$name]); 32 | } 33 | 34 | /** 35 | * @param mixed $name 36 | * 37 | * @throws SiteInvalidPropertyException 38 | */ 39 | public function __get($name) 40 | { 41 | if (!isset($this->_properties[$name])) { 42 | throw new SiteInvalidPropertyException( 43 | "There is no content available for '{$name}'.", 44 | 0, 45 | $this, 46 | $name 47 | ); 48 | } 49 | 50 | return $this->_properties[$name]; 51 | } 52 | 53 | public function __set($name, $content) 54 | { 55 | $this->_properties[$name] = $content; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ## i18n Makefile 3 | ## 4 | 5 | DOMAIN = site 6 | XGETTEXT = xgettext --from-code=UTF-8 7 | MSGFMT = msgfmt -c 8 | MSGMERGE = msgmerge --no-fuzzy-matching 9 | MSGUNIQ = msguniq 10 | INSTALL = install 11 | INSTALLDIR = mkdir -p 12 | DATA_MODE = 660 13 | 14 | TRANSLATIONS := en_CA en_US en_GB de_DE 15 | MO_FILES := $(patsubst %,%.mo, $(TRANSLATIONS)) 16 | 17 | POTFILE = $(DOMAIN).pot 18 | 19 | TARGETS = $(MO_FILES) 20 | 21 | LOCALEDIR = ../locale 22 | 23 | ###################################### 24 | 25 | all: 26 | make $(TARGETS) 27 | 28 | potfiles: 29 | rm -f $@-t1 $@-t2 $@ 30 | (sed -e '/^#/d' < potfiles.in) > $@-t1 31 | (for i in `cat $@-t1`; do find $$i -name \*.php; done) >> $@-t2 32 | (for i in `cat $@-t2`; do php preprocess.php $$i > $$i.gettext; echo $$i.gettext; done) >> $@ 33 | rm -f $@-t1 $@-t2 34 | 35 | pot: $(POTFILE) 36 | 37 | $(POTFILE): potfiles 38 | $(XGETTEXT) -o $(POTFILE) -L Php -f potfiles 39 | $(MSGUNIQ) -o $(POTFILE) $(POTFILE) 40 | for i in `cat potfiles`; do rm -f $$i; done 41 | rm -f potfiles 42 | 43 | %.mo: %.po 44 | $(MSGFMT) -o $@ $< 45 | 46 | install: 47 | for i in $(TRANSLATIONS); do \ 48 | $(INSTALLDIR) $(LOCALEDIR)/$$i/LC_MESSAGES ; \ 49 | $(INSTALL) -m $(DATA_MODE) $$i.mo $(LOCALEDIR)/$$i/LC_MESSAGES/$(DOMAIN).mo ; \ 50 | done 51 | 52 | update: pot 53 | for i in $(TRANSLATIONS); do \ 54 | $(MSGMERGE) -U $$i.po $(POTFILE); \ 55 | done 56 | 57 | clean: 58 | rm -f potfiles *.mo *.pot 59 | -------------------------------------------------------------------------------- /Site/SiteFulltextSearchResult.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {$data->html_head_entries} 22 | {$data->html_title} 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |

{$data->title}

32 | {$data->content} 33 |
34 |
35 | 36 | 37 | 38 | HTML; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Site/SiteSearchIndexer.php: -------------------------------------------------------------------------------- 1 | SiteCommandLineConfigModule::class, 31 | 'database' => SiteDatabaseModule::class, 32 | ] 33 | ); 34 | } 35 | 36 | /** 37 | * Configures modules of this application before they are initialized. 38 | * 39 | * @param SiteConfigModule $config the config module of this application to 40 | * use for configuration other modules 41 | */ 42 | protected function configure(SiteConfigModule $config) 43 | { 44 | parent::configure($config); 45 | $this->database->dsn = $config->database->dsn; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteMediaCdnTaskWrapper.php: -------------------------------------------------------------------------------- 1 | getOption('lazy_load')) { 23 | $this->loadAllSubDataObjects( 24 | 'media', 25 | $this->db, 26 | 'select * from Media where id in (%s)', 27 | SwatDBClassMap::get(SiteMediaWrapper::class) 28 | ); 29 | 30 | $this->loadAllSubDataObjects( 31 | 'encoding', 32 | $this->db, 33 | 'select * from MediaEncoding where id in (%s)', 34 | SwatDBClassMap::get(SiteMediaEncodingWrapper::class) 35 | ); 36 | } 37 | } 38 | 39 | protected function init() 40 | { 41 | parent::init(); 42 | $this->row_wrapper_class = SwatDBClassMap::get(SiteMediaCdnTask::class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sql/pgsql/functions/getArticleNavBar.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns navbar information from an article. 3 | * 4 | * @param_parent INTEGER: the id of the article to search from. 5 | * 6 | * @returned_row type_article_navbar: a row containing id, shortname and title 7 | * 8 | * Returns a set of returned_rows ordered correctly to go in the navbar. 9 | * Checking if the parent articles are enabled is left up to sp_article_find. 10 | * If the article is not found, returns an empty recordset. 11 | * 12 | * This procedure uses recursion to output entries in the correct order for 13 | * applications. 14 | */ 15 | CREATE TYPE type_article_navbar AS (id INTEGER, parent INTEGER, shortname VARCHAR(255), title VARCHAR(255)); 16 | 17 | CREATE OR REPLACE FUNCTION getArticleNavBar(INTEGER) RETURNS SETOF type_article_navbar AS $$ 18 | DECLARE 19 | param_id ALIAS FOR $1; 20 | local_found BOOLEAN; 21 | returned_row type_article_navbar%ROWTYPE; 22 | parent_row type_article_navbar%ROWTYPE; 23 | BEGIN 24 | -- get current article results 25 | SELECT INTO returned_row id, parent, shortname, title 26 | FROM Article 27 | WHERE id = param_id; 28 | 29 | local_found := FOUND; 30 | 31 | -- get parent article results 32 | IF returned_row.parent IS NOT NULL THEN 33 | FOR parent_row IN SELECT * FROM getArticleNavBar(returned_row.parent) LOOP 34 | RETURN NEXT parent_row; 35 | END LOOP; 36 | END IF; 37 | 38 | IF local_found THEN 39 | RETURN NEXT returned_row; 40 | END IF; 41 | 42 | RETURN; 43 | END; 44 | $$ LANGUAGE 'plpgsql'; 45 | -------------------------------------------------------------------------------- /Site/admin/components/Account/search.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search Accounts 7 | 8 | 9 | Add an Account 10 | Account/Edit 11 | create 12 | 13 | 14 | 15 | 16 | Name 17 | 18 | 19 | 20 | Email Address 21 | 22 | 23 | 24 | Instance 25 | false 26 | 27 | 28 | 29 | 30 | Search 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteMediaSetWrapper.php: -------------------------------------------------------------------------------- 1 | getOption('lazy_load')) { 23 | $this->loadAllSubRecordsets( 24 | 'encodings', 25 | $this->getMediaEncodingWrapperClass(), 26 | 'MediaEncoding', 27 | 'media_set', 28 | '', 29 | $this->getMediaEncodingOrderBy() 30 | ); 31 | } 32 | } 33 | 34 | protected function getMediaEncodingWrapperClass() 35 | { 36 | return SwatDBClassMap::get(SiteMediaEncodingWrapper::class); 37 | } 38 | 39 | protected function getMediaEncodingOrderBy() 40 | { 41 | return 'media_set'; 42 | } 43 | 44 | protected function init() 45 | { 46 | parent::init(); 47 | 48 | $this->row_wrapper_class = 49 | SwatDBClassMap::get(SiteMediaSet::class); 50 | 51 | $this->index_field = 'id'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Site/SiteArticleSearchEngine.php: -------------------------------------------------------------------------------- 1 | fulltext_result !== null) { 26 | $clause .= ' ' . 27 | $this->fulltext_result->getJoinClause( 28 | 'Article.id', 29 | 'article' 30 | ); 31 | } 32 | 33 | return $clause; 34 | } 35 | 36 | protected function getWhereClause() 37 | { 38 | return sprintf( 39 | 'where Article.searchable = %s', 40 | $this->app->db->quote(true, 'boolean') 41 | ); 42 | } 43 | 44 | protected function getOrderByClause() 45 | { 46 | if ($this->fulltext_result === null) { 47 | $clause = sprintf('order by Article.title'); 48 | } else { 49 | $clause = 50 | $this->fulltext_result->getOrderByClause('Article.title'); 51 | } 52 | 53 | return $clause; 54 | } 55 | 56 | protected function getMemcacheNs() 57 | { 58 | return 'article'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Site/pages/SiteDBEditPage.php: -------------------------------------------------------------------------------- 1 | app->db); 16 | 17 | try { 18 | $this->saveData($form); 19 | $transaction->commit(); 20 | } catch (SwatDBException $e) { 21 | if ($this->app->hasModule('SiteMessagesModule')) { 22 | $messages = $this->app->getModule('SiteMessagesModule'); 23 | $messages->add($this->getRollbackMessage($form)); 24 | } 25 | $transaction->rollback(); 26 | $this->handleDBException($e); 27 | } catch (Throwable $e) { 28 | $this->handleException($transaction, $e); 29 | } 30 | } 31 | 32 | abstract protected function saveData(SwatForm $form); 33 | 34 | protected function getRollbackMessage(SwatForm $form) 35 | { 36 | return new SwatMessage( 37 | Site::_('An error has occurred. The item was not saved.'), 38 | 'system-error' 39 | ); 40 | } 41 | 42 | protected function handleDBException(SwatDBException $e) 43 | { 44 | $e->processAndContinue(); 45 | } 46 | 47 | protected function handleException( 48 | SwatDBTransaction $transaction, 49 | Throwable $e 50 | ) { 51 | throw $e; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /www/styles/site-content-slider.css: -------------------------------------------------------------------------------- 1 | .site-content-slider { 2 | position: relative; 3 | overflow: hidden; 4 | } 5 | 6 | .site-content-slider .slider-next-prev, 7 | .site-content-slider .slider-nav { 8 | position: absolute; 9 | bottom: 10px; 10 | width: 100%; 11 | text-align: center; 12 | } 13 | 14 | .site-content-slider .slider-next-prev span, 15 | .site-content-slider .slider-next-prev a { 16 | text-decoration: none; 17 | color: #fff; 18 | margin: 0 5px; 19 | } 20 | 21 | .site-content-slider .slider-next-prev a:hover { 22 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4); 23 | } 24 | 25 | .site-content-slider .slider-next-prev span { 26 | opacity: 0.3; 27 | filter: alpha(opacity=30); 28 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; 29 | } 30 | 31 | .site-content-slider .slider-nav a { 32 | width: 20px; 33 | height: 20px; 34 | display: inline-block; 35 | } 36 | 37 | .site-content-slider .slider-nav a span { 38 | display: inline-block; 39 | width: 8px; 40 | height: 8px; 41 | margin: 6px; 42 | background: #fff; 43 | border-radius: 50%; 44 | border: 1px solid #999; 45 | opacity: 0.5; 46 | filter: alpha(opacity=50); 47 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; 48 | } 49 | 50 | .site-content-slider .slider-nav a:hover span { 51 | opacity: 1; 52 | filter: alpha(opacity=100); 53 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 54 | } 55 | 56 | .site-content-slider .slider-nav a.selected span { 57 | opacity: 1; 58 | filter: alpha(opacity=100); 59 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 60 | border: 1px solid #666; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /sql/mysql/functions/findArticle.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Finds an article in the article tree. 3 | * 4 | * @param_source VARCHAR(255): a string containing the path to the article delimited by forward slashes. 5 | * 6 | * Returns an integer containing the id of the searched for article. 7 | * If the article is not found, returns NULL. 8 | */ 9 | CREATE FUNCTION findArticle (param_source VARCHAR(255)) RETURNS INTEGER 10 | BEGIN 11 | DECLARE local_source VARCHAR(255); 12 | DECLARE local_shortname VARCHAR(255); 13 | DECLARE local_parent INTEGER; 14 | DECLARE local_pos INTEGER; 15 | DECLARE local_id INTEGER; 16 | 17 | -- Find the first forward slash in the source string. 18 | SET local_source = CONCAT(param_source, '/'); 19 | SET local_pos = POSITION('/' IN local_source); 20 | 21 | SET local_id = NULL; 22 | 23 | WHILE local_pos != 0 DO 24 | BEGIN 25 | -- Get shortname from beginning of source string. 26 | SET local_shortname = SUBSTRING(local_source FROM 1 FOR (local_pos - 1)); 27 | -- Get the remainder of the source string. 28 | SET local_source = SUBSTRING(local_source FROM local_pos + 1 FOR character_length(local_source) - local_pos); 29 | 30 | -- Get the id of the parent 31 | SELECT id INTO local_id 32 | FROM Article 33 | WHERE (Article.parent = local_id OR (local_id is null AND parent is null)) 34 | AND shortname = local_shortname 35 | AND id != 0; 36 | 37 | IF local_id IS NULL THEN 38 | RETURN NULL; 39 | END IF; 40 | 41 | -- Find next forward slash in the source string. 42 | SET local_pos = POSITION('/' IN local_source); 43 | END; 44 | END WHILE; 45 | 46 | RETURN local_id; 47 | END; 48 | -------------------------------------------------------------------------------- /sql/pgsql/functions/findArticle.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Finds an article in the article tree. 3 | * 4 | * @param_source VARCHAR(255): a string containing the path to the article delimited by forward slashes. 5 | * 6 | * Returns an integer containing the id of the searched for article. 7 | * If the article is not found, returns NULL. 8 | */ 9 | CREATE OR REPLACE FUNCTION findArticle (VARCHAR(255)) RETURNS INTEGER AS $$ 10 | DECLARE 11 | param_source ALIAS FOR $1; 12 | local_source VARCHAR(255); 13 | local_shortname VARCHAR(255); 14 | local_parent INTEGER; 15 | local_pos INTEGER; 16 | local_id INTEGER; 17 | BEGIN 18 | -- Find the first forward slash in the source string. 19 | local_source := param_source || '/'; 20 | local_pos := POSITION('/' IN local_source); 21 | 22 | local_id := NULL; 23 | 24 | WHILE local_pos != 0 LOOP 25 | BEGIN 26 | -- Get shortname from beginning of source string. 27 | local_shortname := SUBSTRING(local_source FROM 0 FOR local_pos); 28 | -- Get the remainder of the source string. 29 | local_source := SUBSTRING(local_source FROM local_pos + 1 FOR character_length(local_source) - local_pos + 1); 30 | 31 | -- Get the id of the parent 32 | SELECT INTO local_id id 33 | FROM Article 34 | WHERE (Article.parent = local_id OR (local_id is null AND parent is null)) 35 | AND shortname = local_shortname 36 | AND id != 0; 37 | 38 | IF local_id is null THEN 39 | RETURN null; 40 | END IF; 41 | 42 | -- Find next forward slash in the source string. 43 | local_pos := POSITION('/' IN local_source); 44 | END; 45 | END LOOP; 46 | 47 | RETURN local_id; 48 | END; 49 | $$ LANGUAGE 'plpgsql'; 50 | -------------------------------------------------------------------------------- /Site/admin/components/Comment/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Filter Comments 7 | 8 | 9 | Keywords 10 | 11 | 12 | 13 | Author 14 | 15 | 16 | 17 | Show 18 | 19 | 20 | 21 | 22 | Search 23 | 24 | 25 | 26 | 27 | 28 | Manage Comments 29 | 30 | 31 | 32 | 33 | Comment 34 | 50 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Site/pages/contact.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Send us a Message 8 | 9 | Send To 10 | 11 | 12 | 13 | Your Email 14 | 15 | true 16 | 17 | 18 | 19 | Subject 20 | 21 | true 22 | false 23 | 24 | 25 | 26 | Message 27 | 28 | true 29 | 30 | 31 | 32 | 33 | Send Message 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Site/SitePathEntry.php: -------------------------------------------------------------------------------- 1 | id = $id; 61 | $this->parent = $parent; 62 | $this->shortname = $shortname; 63 | $this->title = $title; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteImageType.php: -------------------------------------------------------------------------------- 1 | checkDB(); 46 | 47 | $found = false; 48 | 49 | $sql = 'select * from %s where lower(mime_type) = lower(%s)'; 50 | 51 | $sql = sprintf( 52 | $sql, 53 | $this->table, 54 | $this->db->quote($mime_type, 'text') 55 | ); 56 | 57 | $row = SwatDB::queryRow($this->db, $sql); 58 | 59 | if ($row !== null) { 60 | $this->initFromRow($row); 61 | $this->generatePropertyHashes(); 62 | $found = true; 63 | } 64 | 65 | return $found; 66 | } 67 | 68 | protected function init() 69 | { 70 | $this->table = 'ImageType'; 71 | $this->id_field = 'integer:id'; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Site/dataobjects/SiteAttachmentSet.php: -------------------------------------------------------------------------------- 1 | checkDB(); 51 | 52 | $found = false; 53 | 54 | $sql = 'select * from %s where shortname = %s'; 55 | 56 | $sql = sprintf( 57 | $sql, 58 | $this->table, 59 | $this->db->quote($shortname, 'text') 60 | ); 61 | 62 | $row = SwatDB::queryRow($this->db, $sql); 63 | 64 | if ($row !== null) { 65 | $this->initFromRow($row); 66 | $this->generatePropertyHashes(); 67 | $found = true; 68 | } 69 | 70 | return $found; 71 | } 72 | 73 | protected function init() 74 | { 75 | $this->table = 'AttachmentSet'; 76 | $this->id_field = 'integer:id'; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Site/pages/SiteXMLRPCServer.php: -------------------------------------------------------------------------------- 1 | 'utf-8', 30 | 'input' => 'XML_RPC2_Server_Input_PhpInput', 31 | // Explicitly select the PHP backend. The native backend has 32 | // trouble with UTF-8 encoding and with properly parsing 33 | // method parameters. 34 | 'backend' => 'Php', 35 | ]); 36 | 37 | $this->layout->startCapture('response'); 38 | $server->handleCall(); 39 | $this->layout->endCapture(); 40 | } 41 | 42 | /** 43 | * @xmlrpc.hidden 44 | */ 45 | public function __toString(): string 46 | { 47 | return parent::__toString(); 48 | } 49 | 50 | /** 51 | * @xmlrpc.hidden 52 | */ 53 | protected function createLayout() 54 | { 55 | return new SiteXMLRPCServerLayout($this->app); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Site/admin/components/Account/details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Edit Account 9 | Account/Edit?id=%s 10 | edit 11 | 12 | 13 | Email New Password 14 | Account/EmailPassword?id=%s 15 | email 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Name 24 | 25 | fullname 26 | 27 | 28 | 29 | Email 30 | 31 | email 32 | 33 | 34 | 35 | Created On 36 | 37 | createdate 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Site/exceptions/SiteInvalidPropertyException.php: -------------------------------------------------------------------------------- 1 | object = $object; 41 | $this->property = $property; 42 | } 43 | 44 | /** 45 | * Gets the object the property is invalid for. 46 | * 47 | * @return mixed the object the property is invalid for 48 | */ 49 | public function getObject() 50 | { 51 | return $this->object; 52 | } 53 | 54 | /** 55 | * Gets the name of the property that is invalid. 56 | * 57 | * @return string the name of the property that is invalid 58 | */ 59 | public function getProperty() 60 | { 61 | return $this->property; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Site/pages/SiteSearchPage.php: -------------------------------------------------------------------------------- 1 | form_ui = new SwatUI(); 31 | $this->form_ui->loadFromXML($this->form_ui_xml); 32 | 33 | $form = $this->form_ui->getWidget('search_form'); 34 | $form->action = $this->source; 35 | 36 | $this->form_ui->init(); 37 | } 38 | 39 | // process phase 40 | 41 | public function process() 42 | { 43 | parent::process(); 44 | 45 | $this->form_ui->process(); 46 | 47 | /* 48 | * Nothing else to do... 49 | * the parent class result page is driven by the GET variables this 50 | * form provided. 51 | */ 52 | } 53 | 54 | // build phase 55 | 56 | public function build() 57 | { 58 | $this->layout->startCapture('content'); 59 | $this->form_ui->display(); 60 | $this->layout->endCapture(); 61 | 62 | parent::build(); 63 | } 64 | 65 | // finalize phase 66 | 67 | public function finalize() 68 | { 69 | parent::finalize(); 70 | $this->layout->addHtmlHeadEntrySet( 71 | $this->form_ui->getRoot()->getHtmlHeadEntrySet() 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Site/SiteCdnModule.php: -------------------------------------------------------------------------------- 1 | app = $app; 30 | $this->layout = $layout ?? $this->createLayout(); 31 | $this->arguments = $arguments; 32 | } 33 | 34 | protected function createLayout() 35 | { 36 | return new SiteLayout($this->app, SiteDefaultTemplate::class); 37 | } 38 | 39 | // build phase 40 | 41 | public function build() 42 | { 43 | $this->buildTitle(); 44 | $this->buildMetaDescription(); 45 | 46 | if (isset($this->layout->navbar)) { 47 | $this->buildNavBar(); 48 | } 49 | 50 | $this->buildContent(); 51 | } 52 | 53 | protected function buildTitle() {} 54 | 55 | protected function buildMetaDescription() {} 56 | 57 | protected function buildContent() {} 58 | 59 | protected function buildNavBar() {} 60 | } 61 | -------------------------------------------------------------------------------- /Site/pages/SiteXhtmlExceptionPage.php: -------------------------------------------------------------------------------- 1 | layout->data->title = $this->getTitle(); 16 | } 17 | 18 | protected function buildContent() 19 | { 20 | $this->layout->startCapture('content'); 21 | $this->display(); 22 | $this->layout->endCapture(); 23 | } 24 | 25 | protected function buildNavBar() 26 | { 27 | if (isset($this->layout->navbar)) { 28 | $this->layout->navbar->createEntry($this->getTitle()); 29 | } 30 | } 31 | 32 | protected function display() 33 | { 34 | printf('

%s

', $this->getSummary()); 35 | 36 | $this->displaySuggestions(); 37 | 38 | if ($this->exception instanceof SwatException 39 | && !($this->exception instanceof SiteNotAuthorizedException)) { 40 | $this->exception->processAndContinue(); 41 | } 42 | } 43 | 44 | protected function displaySuggestions() 45 | { 46 | $suggestions = $this->getSuggestions(); 47 | 48 | if (count($suggestions) == 0) { 49 | return; 50 | } 51 | 52 | echo ''; 61 | } 62 | 63 | // finalize phase 64 | 65 | public function finalize() 66 | { 67 | parent::finalize(); 68 | $this->layout->addBodyClass('exception-page'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /dependencies/site.yaml: -------------------------------------------------------------------------------- 1 | ## 2 | ## Static Web-resource dependencies for the Site package 3 | ## 4 | ## Copyright (c) 2010 silverorange 5 | ## 6 | ## This library is free software; you can redistribute it and/or modify 7 | ## it under the terms of the GNU Lesser General Public License as 8 | ## published by the Free Software Foundation; either version 2.1 of the 9 | ## License, or (at your option) any later version. 10 | ## 11 | ## This library is distributed in the hope that it will be useful, 12 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | ## Lesser General Public License for more details. 15 | ## 16 | ## You should have received a copy of the GNU Lesser General Public 17 | ## License along with this library; if not, write to the Free Software 18 | ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | ## 20 | Site: 21 | Depends: 22 | # Package Dependencies 23 | - Swat 24 | - XML_RPCAjax 25 | 26 | Provides: 27 | 28 | # JavaScript resources 29 | packages/site/javascript/site-gravatar-entry.js: 30 | packages/site/javascript/site-tag-entry.js: 31 | packages/site/javascript/site-upload-progress-bar.js: 32 | packages/site/javascript/site-content-slider.js: 33 | packages/site/admin/javascript/site-comment-display.js: 34 | packages/site/admin/javascript/site-comment-status-slider.js: 35 | 36 | # Style-sheet resources 37 | packages/site/styles/site-account-login-page.css: 38 | packages/site/styles/site-account-reset-password-page.css: 39 | packages/site/styles/site-contact-page.css: 40 | packages/site/styles/site-image-cell-renderer.css: 41 | packages/site/styles/site-search-results-page.css: 42 | packages/site/styles/site-tag-entry.css: 43 | packages/site/styles/site-content-slider.css: 44 | packages/site/admin/styles/site-comment-display.css: 45 | packages/site/admin/styles/site-comment-status-slider.css: 46 | -------------------------------------------------------------------------------- /Site/admin/components/Comment/edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Comment 7 | 8 | 9 | Name 10 | 11 | 255 12 | true 13 | 14 | 15 | 16 | Website 17 | 18 | 255 19 | 20 | 21 | 22 | Email 23 | 24 | 255 25 | 26 | 27 | 28 | Comment 29 | 30 | true 31 | 15 32 | 33 | 34 | 35 | Status 36 | 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Site/SiteTimerCheckpoint.php: -------------------------------------------------------------------------------- 1 | name = $name; 43 | $this->time = $time; 44 | $this->memory_usage = $memory_usage; 45 | } 46 | 47 | /** 48 | * Gets the time when this checkpoint was created. 49 | * 50 | * @return float the time when this checkpoint was created 51 | */ 52 | public function getTime() 53 | { 54 | return $this->time; 55 | } 56 | 57 | /** 58 | * Gets the name of this checkpoint. 59 | * 60 | * return string the name of this checkpoint. 61 | */ 62 | public function getName() 63 | { 64 | return $this->name; 65 | } 66 | 67 | /** 68 | * Gets the memory use of this checkpoint. 69 | * 70 | * return string the memory use of this checkpoint. 71 | */ 72 | public function getMemoryUsage() 73 | { 74 | return $this->memory_usage; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /sql/fixtures/SiteBotrMediaEncoding.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 = 'public'), 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 = 'public'), 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 = 'public'), 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 = 'public'), 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 = 'public'), 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 = 'public'), 90 | (select id from MediaType where mime_type = 'video/mp4'), 91 | '320-wide (QVGA)', 92 | '320', 93 | true, 94 | 320 95 | ); 96 | -------------------------------------------------------------------------------- /Site/SiteVideoMediaLocalMover.php: -------------------------------------------------------------------------------- 1 | file_base = $file_base; 20 | } 21 | 22 | public function getFileBase() 23 | { 24 | if ($this->file_base === null) { 25 | throw new SiteException('File base has not been set.'); 26 | } 27 | 28 | return $this->file_base; 29 | } 30 | 31 | protected function getOldPath(SiteVideoMedia $media, $shortname) 32 | { 33 | return sprintf( 34 | '%s/%s/%s/%s', 35 | $this->getFileBase(), 36 | $media->media_set->shortname, 37 | $shortname, 38 | $this->getOldFilename($media, $shortname) 39 | ); 40 | } 41 | 42 | protected function getNewPath(SiteVideoMedia $media, $shortname) 43 | { 44 | return sprintf( 45 | '%s/%s/full/%s', 46 | $this->getFileBase(), 47 | $media->id, 48 | $this->getNewFilename($media, $shortname) 49 | ); 50 | } 51 | 52 | protected function hasFile($path) 53 | { 54 | return is_file($path); 55 | } 56 | 57 | protected function moveFile(SiteVideoMedia $media, $old_path, $new_path) 58 | { 59 | $parts = pathinfo($new_path); 60 | $directory = $parts['dirname']; 61 | 62 | if (!file_exists($directory)) { 63 | mkdir($directory, 0o777, true); 64 | } 65 | 66 | copy($old_path, $new_path); 67 | } 68 | 69 | protected function cleanUp($path) 70 | { 71 | if (file_exists($path)) { 72 | unlink($path); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Site/admin/components/Account/suspicious.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Suspicious Accounts 6 | 7 | A suspicious account has 5 or more logins in the last week, from 5 or more locations, and 5 or more different web browsers. 8 | 9 | 10 | 11 | 12 | Name 13 | 14 | fullname 15 | Account/Details?id=%s 16 | id 17 | person 18 | 19 | 20 | 21 | Email 22 | 23 | email 24 | 25 | 26 | 27 | Instance 28 | false 29 | 30 | instance.title 31 | 32 | 33 | 34 | 35 | details 36 | 37 | 38 | 39 | 40 | Account/Suspicious 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Site/admin/components/Ad/details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ad Details 7 | 8 | 9 | Delete Ad 10 | Ad/Delete?id=%s 11 | delete 12 | 13 | 14 | 15 | 16 | Referrers in the Last 17 | 18 | 19 | 20 | period 21 | 22 | 23 | 24 | Referrers 25 | 26 | referrers 27 | 28 | 29 | 30 | 31 | 32 | Top HTTP Referers 33 | 34 | <none> 35 | 36 | URI 37 | 38 | uri 39 | 40 | 41 | 42 | Number of Referrrals 43 | 44 | referer_count 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Site/admin/components/Ad/Delete.php: -------------------------------------------------------------------------------- 1 | getDeleteSql(); 18 | $num = SwatDB::exec($this->app->db, $sql); 19 | $message = new SwatMessage(sprintf( 20 | Site::ngettext( 21 | 'One ad has been deleted.', 22 | '%s ads have been deleted.', 23 | $num 24 | ), 25 | SwatString::numberFormat($num) 26 | ), 'notice'); 27 | 28 | $this->app->messages->add($message); 29 | } 30 | 31 | protected function getDeleteSql() 32 | { 33 | $item_list = $this->getItemList('integer'); 34 | 35 | return sprintf( 36 | 'delete from Ad where id in (%s)', 37 | $item_list 38 | ); 39 | } 40 | 41 | // build phase 42 | 43 | protected function buildInternal() 44 | { 45 | parent::buildInternal(); 46 | 47 | $dep = $this->getDependencies(); 48 | 49 | $message = $this->ui->getWidget('confirmation_message'); 50 | $message->content = $dep->getMessage(); 51 | $message->content_type = 'text/xml'; 52 | 53 | if ($dep->getStatusLevelCount(AdminDependency::DELETE) == 0) { 54 | $this->switchToCancelButton(); 55 | } 56 | } 57 | 58 | protected function getDependencies() 59 | { 60 | $item_list = $this->getItemList('integer'); 61 | 62 | $dep = new AdminListDependency(); 63 | $dep->setTitle(Site::_('ad'), Site::_('ads')); 64 | $dep->entries = AdminListDependency::queryEntries( 65 | $this->app->db, 66 | 'Ad', 67 | 'integer:id', 68 | null, 69 | 'text:title', 70 | 'id', 71 | 'id in (' . $item_list . ')', 72 | AdminDependency::DELETE 73 | ); 74 | 75 | return $dep; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Site/admin/components/Account/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Accounts 6 | false 7 | 8 | 9 | 10 | 11 | 12 | id 13 | 14 | 15 | 16 | Name 17 | 18 | fullname 19 | Account/Details?id=%s 20 | id 21 | person 22 | 23 | 24 | 25 | Email 26 | 27 | email 28 | 29 | 30 | 31 | Instance 32 | false 33 | 34 | instance.title 35 | 36 | 37 | 38 | 39 | 40 | delete… 41 | 42 | 43 | 44 | Account 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Site/pages/account-login.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Email Address 9 | 10 | 255 11 | 40 12 | 1 13 | 14 | 15 | 16 | Password 17 | 18 | 255 19 | 2 20 | 21 | 22 | text/xml 23 | 24 | 25 | 26 | 27 | Sign In 28 | 5 29 | 30 | 31 | 32 | 33 | 34 | Don’t Have an Account? 35 | 36 | Creating an account will make it quicker and easier to order again when you return.

]]>
37 | text/xml 38 |
39 | 40 | 41 | 42 | Create an Account 43 | 44 | 45 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /Site/pages/account-change-password.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Enter Your Current Password 9 | Forgot your current password?]]> 10 | text/xml 11 | 12 | false 13 | 4 14 | 255 15 | true 16 | 17 | 18 | 19 | Choose a New Password 20 | 21 | false 22 | 4 23 | 255 24 | true 25 | 26 | 27 | 28 | Re-type the New Password 29 | 30 | false 31 | 4 32 | 255 33 | true 34 | 35 | 36 | 37 | 38 | Update Password 39 | 40 | 41 | 42 | 43 | 44 | --------------------------------------------------------------------------------