diff options
Diffstat (limited to 'app/frontend/controls')
40 files changed, 1200 insertions, 0 deletions
diff --git a/app/frontend/controls/AddToFilter.php b/app/frontend/controls/AddToFilter.php new file mode 100644 index 0000000..9146f3b --- /dev/null +++ b/app/frontend/controls/AddToFilter.php @@ -0,0 +1,41 @@ +<?php + +Prado::using('System.Web.UI.ActiveControls.TActiveCheckBox'); + +class AddToFilter extends UrlBasedCalendarControl { + + public function setDescription($val) { + $this->setViewState('Description', TPropertyValue::ensureString($val)); + } + + public function getDescription() { + return $this->getViewState('Description'); + } + + public function setUserPreference($sender, $param) { + $user = $this->getUserToManage(); + if ($user && !$user->IsGuest) { + if ($sender->Checked) { + $this->getFacade()->addToPreference($user, $this->getCalendar()->ID); + } else { + $this->getFacade()->removeFromPreference($user, $this->getCalendar()->ID); + } + $this->Page->CallbackClient->jQuery($this->Box, 'removeAttr', 'disabled'); + } + } + + public function getUserToManage() { + return $this->getControlState('user'); + } + + public function setUserToManage($user) { + $this->setControlState('user', $user); + } + + public function getPradoScriptDependencies() { + return ['jquery']; + } + +} + +?> diff --git a/app/frontend/controls/AddToFilter.tpl b/app/frontend/controls/AddToFilter.tpl new file mode 100644 index 0000000..a202aa7 --- /dev/null +++ b/app/frontend/controls/AddToFilter.tpl @@ -0,0 +1,8 @@ +<com:TActiveCheckBox ID="Box" OnCheckedChanged="setUserPreference" CssClass="addToFilterBox"> + <prop:Enabled><%= !$this->UserToManage->IsGuest %></prop:Enabled> + <prop:Checked><%= $this->Facade->isCalendarPreferred($this->UserToManage, $this->getCalendar()->ID) %></prop:Checked> + <prop:ToolTip><%= $this->UserToManage->IsGuest ? Prado::localize('log in to manage your selections') : '' %></prop:ToolTip> +</com:TActiveCheckBox> +<com:TLabel ForControl="Box"> + <prop:Text><%= $this->getDescription() %></prop:Text> +</com:TLabel> diff --git a/app/frontend/controls/CalendarDetails.php b/app/frontend/controls/CalendarDetails.php new file mode 100644 index 0000000..95ee563 --- /dev/null +++ b/app/frontend/controls/CalendarDetails.php @@ -0,0 +1,7 @@ +<?php + +class CalendarDetails extends UrlBasedCalendarControl { + +} + +?> diff --git a/app/frontend/controls/CalendarDetails.tpl b/app/frontend/controls/CalendarDetails.tpl new file mode 100644 index 0000000..2fd755c --- /dev/null +++ b/app/frontend/controls/CalendarDetails.tpl @@ -0,0 +1,13 @@ +<com:THeader2> + <%= $this->getCalendar()->Name %> +</com:THeader2> +<com:TImage> + <prop:ImageUrl><%= $this->getCalendar()->Image %></prop:ImageUrl> +</com:TImage> +<com:THyperLink Target="_blank"> + <prop:Text><%[ Source website ]%></prop:Text> + <prop:NavigateUrl><%= $this->getCalendar()->Website %></prop:NavigateUrl> +</com:THyperLink> +<p> + <%[ Last updated: ]%> <%= $this->getCalendar()->LastUpdated %> +</p> diff --git a/app/frontend/controls/CalendarGrid.php b/app/frontend/controls/CalendarGrid.php new file mode 100644 index 0000000..4ebfacd --- /dev/null +++ b/app/frontend/controls/CalendarGrid.php @@ -0,0 +1,59 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); +Prado::using('Application.facades.EventFacade'); +Prado::using('Application.user.DbUser'); + +class CalendarGrid extends FacadeTemplateControl { + + public function setMonth($month) { + $this->setControlState('Month', $month); + } + + public function getMonth() { + return $this->getControlState('Month'); + } + + public function setYear($year) { + $this->setControlState('Year', $year); + } + + public function getYear() { + return $this->getControlState('Year'); + } + + public function setUserToDisplay(DbUser $user) { + $this->setControlState('User', $user); + } + + public function getUserToDisplay() { + return $this->getControlState('User'); + } + + private function _getGrid() { + return $this->getFacade()->getCalendarListForUser( + $this->UserToDisplay, + $this->Month, + $this->Year + ); + } + + public function onPreRender($param) { + parent::onPreRender($param); + $this->Weeks->DataSource = $this->_getGrid()->Weeks; + $this->Weeks->dataBind(); + } + + public function weekDataBind($sender, $param) { + $param->Item->Days->DataSource = $param->Item->Data; + $param->Item->Days->dataBind(); + } + + public function dayDataBind($sender, $param) { + $param->Item->Events->DataSource = $param->Item->Data->Events; + $param->Item->Events->dataBind(); + } + +} + +?> diff --git a/app/frontend/controls/CalendarGrid.tpl b/app/frontend/controls/CalendarGrid.tpl new file mode 100644 index 0000000..2b3ada8 --- /dev/null +++ b/app/frontend/controls/CalendarGrid.tpl @@ -0,0 +1,28 @@ +<com:TRepeater ID="Weeks" OnItemDataBound="weekDataBind"> + <prop:ItemTemplate> + <div class="gridWeek"> + <com:TRepeater ID="Days" OnItemDataBound="SourceTemplateControl.dayDataBind"> + <prop:ItemTemplate> + <div class="gridDay"> + <%# $this->Data->Date %> + <com:TRepeater ID="Events"> + <prop:ItemTemplate> + <com:TConditional Condition="$this->Data"> + <prop:TrueTemplate> + <com:THtmlElement TagName="div"> + <prop:CssClass>gridEvent <%# $this->Parent->Parent->Data->Date == $this->Data->DateFrom ? 'beginDate' : '' %> <%# $this->Parent->Parent->Data->Date == $this->Data->DateTo ? 'endDate' : '' %></prop:CssClass> + <%# $this->Data->Name %> + </com:THtmlElement> + </prop:TrueTemplate> + <prop:FalseTemplate> + <div class="gridItem"> </div> + </prop:FalseTemplate> + </com:TConditional> + </prop:ItemTemplate> + </com:TRepeater> + </div> + </prop:ItemTemplate> + </com:TRepeater> + </div> + </prop:ItemTemplate> +</com:TRepeater> diff --git a/app/frontend/controls/CalendarGroupFilter.php b/app/frontend/controls/CalendarGroupFilter.php new file mode 100644 index 0000000..6f19bcd --- /dev/null +++ b/app/frontend/controls/CalendarGroupFilter.php @@ -0,0 +1,21 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); + +Prado::using('Application.facades.CalendarFacade'); + +class CalendarGroupFilter extends FacadeTemplateControl { + + public function onPreRender($param) { + parent::onPreRender($param); + $this->Categories->DataSource = $this->Facade->getCategories(); + $this->Categories->dataBind(); + } + + public function getPradoScriptDependencies() { + return ['jquery']; + } + +} + +?> diff --git a/app/frontend/controls/CalendarGroupFilter.tpl b/app/frontend/controls/CalendarGroupFilter.tpl new file mode 100644 index 0000000..ac82465 --- /dev/null +++ b/app/frontend/controls/CalendarGroupFilter.tpl @@ -0,0 +1,16 @@ +<com:TRepeater ID="Categories"> + <prop:HeaderTemplate> + <div class="selectAllGroups"> + <%[ All ]%> + <com:TCheckBox CssClass="box" Checked="True" /> + </div> + </prop:HeaderTemplate> + <prop:ItemTemplate> + <div class="selectGroup"> + <%# $this->Data->Name %> + <com:TCheckBox CssClass="box" Checked="True"> + <prop:Value><%# $this->Data->ID %></prop:Value> + </com:TCheckBox> + </div> + </prop:ItemTemplate> +</com:TRepeater> diff --git a/app/frontend/controls/CalendarLabel.php b/app/frontend/controls/CalendarLabel.php new file mode 100644 index 0000000..667e847 --- /dev/null +++ b/app/frontend/controls/CalendarLabel.php @@ -0,0 +1,13 @@ +<?php + +Prado::using('Application.controls.UrlBasedCalendarControl'); + +class CalendarLabel extends UrlBasedCalendarControl { + + public function getPradoScriptDependencies() { + return ['jquery']; + } + +} + +?> diff --git a/app/frontend/controls/CalendarLabel.tpl b/app/frontend/controls/CalendarLabel.tpl new file mode 100644 index 0000000..69e0147 --- /dev/null +++ b/app/frontend/controls/CalendarLabel.tpl @@ -0,0 +1,12 @@ +<com:TPanel CssClass="calendar"> + <prop:Attributes.data-group><%= $this->Calendar->GroupID %></prop:Attributes.data-group> + <com:THyperLink> + <prop:NavigateUrl><%= $this->Service->constructUrl('Calendar', ['calendar' => $this->Calendar->Url]) %></prop:NavigateUrl> + <prop:Text><%= $this->Calendar->Name %></prop:Text> + </com:THyperLink> + <com:AddToFilter> + <prop:Facade><%= $this->Facade %></prop:Facade> + <prop:CalendarUrl><%= $this->Calendar->Url %></prop:CalendarUrl> + <prop:UserToManage><%= $this->User %></prop:UserToManage> + </com:AddToFilter> +</com:TPanel> diff --git a/app/frontend/controls/CalendarScaffold.php b/app/frontend/controls/CalendarScaffold.php new file mode 100644 index 0000000..7f15a32 --- /dev/null +++ b/app/frontend/controls/CalendarScaffold.php @@ -0,0 +1,142 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); + +Prado::using('System.Web.UI.ActiveControls.TActiveDataGrid'); +Prado::using('System.Web.UI.ActiveControls.TActiveTextBox'); +Prado::using('Application.components.SafeActiveFileUpload'); + +Prado::using('Application.facades.CalendarFacade'); + +class CalendarScaffold extends FacadeTemplateControl { + + public function onPreRender($param) { + parent::onPreRender($param); + if (!$this->Page->IsPostBack && !$this->Page->IsCallBack) { + $this->_rebindData(); + } + } + + private function _rebindCalendars(array $calendars) { + $this->Calendars->DataSource = $calendars; + $this->Calendars->dataBind(); + } + + private function _rebindCategoryList(array $categories) { + foreach ($this->Calendars->Columns as $column) { + if ($column->ID === 'Category' + && $column instanceof TActiveDropDownListColumn) { + $column->ListDataSource = $categories; + } + } + } + + private function _rebindData(bool $refresh = FALSE) { + $this->_rebindCategoryList( + $this->_getCategories() + ); + $this->_rebindCalendars( + $this->_getCalendars($refresh) + ); + } + + private function _getCalendars(bool $refresh = FALSE) { + if ($refresh) { + $this->clearViewState('Calendars'); + } + $calendars = $this->getViewState( + 'Calendars', + $this->getFacade()->getAll() + ); + $this->setViewState('Calendars', $calendars); + return $calendars; + } + + private function _getCategories() { + $categories = $this->getViewState( + 'Categories', + $this->getFacade()->getCategories() + ); + $this->setViewState('Categories', $categories); + return $categories; + } + + public function editRow($sender, $param) { + $this->Calendars->EditItemIndex = $param->Item->ItemIndex; + $this->_rebindData(); + } + + private function _compileSaveData(TDataGridItem $item) { + return [ + 'CategoryID' => $item->Category->DropDownList->SelectedValue, + 'Visible' => $item->Visible->CheckBox->Checked, + 'CustomName' => $item->CustomName->TextBox->SafeText, + 'CustomUrl' => $item->CustomUrl->TextBox->SafeText, + 'CustomImage' => $item->CustomImage->Value->SafeText + ]; + } + + public function saveRow($sender, $param) { + $calendar = $this->getFacade()->get( + $sender->DataKeys[$param->Item->ItemIndex] + ); + if ($calendar) { + foreach ($calendar as $c) { + $c->saveData($this->_compileSaveData($param->Item)); + } + } else { + throw new TInvalidDataValueException( + Prado::localize('Calendar not found') + ); + } + $this->Calendars->EditItemIndex = -1; + $this->_rebindData(TRUE); + } + + public function cancelRowEdit($sender, $param) { + $this->Calendars->EditItemIndex = -1; + $this->_rebindData(); + } + + public function toggleDefaultState($sender, $param) { + $calendar = $this->getFacade()->get($sender->CustomData); + if ($calendar) { + $calendar[0]->Visible = $sender->Checked; + $calendar[0]->save(); + $this->_rebindData(TRUE); + } + } + + public function uploadRowFile($sender, $param) { + $fileType = $sender->getFileType(); + if (preg_match('/^image\//', $fileType)) { + $calendar = $this->getFacade()->get($sender->CustomData); + if ($calendar) { + $targetFile = $calendar[0]->getCustomImagePath( + $sender->getLocalName(), + $fileType + ); + if ($sender->saveAs($targetFile)) { + $sender->NamingContainer->CustomImage->Value->Text = basename( + $targetFile + ); + } + } else { + throw new TInvalidDataValueException( + Prado::localize('Calendar not found') + ); + } + } else { + throw new TInvalidDataTypeException( + Prado::localize('Invalid file type') + ); + } + } + + protected function getPradoScriptDependencies() { + return ['jquery']; + } + +} + +?> diff --git a/app/frontend/controls/CalendarScaffold.tpl b/app/frontend/controls/CalendarScaffold.tpl new file mode 100644 index 0000000..6a22bc2 --- /dev/null +++ b/app/frontend/controls/CalendarScaffold.tpl @@ -0,0 +1,81 @@ +<com:TPanel + CssClass="calendarScaffold"> + <com:TActiveDataGrid ID="Calendars" + DataKeyField="UID" + AutoGenerateColumns="false" + OnEditCommand="editRow" + OnCancelCommand="cancelRowEdit" + OnUpdateCommand="saveRow"> + <com:TActiveBoundColumn ID="Name" + ReadOnly="true" + DataField="Name"> + <prop:HeaderText><%[ Calendar ]%></prop:HeaderText> + </com:TActiveBoundColumn> + <com:TActiveHyperLinkColumn ID="Website" + Target="_blank" + DataNavigateUrlField="Website"> + <prop:HeaderText><%[ WWW ]%></prop:HeaderText> + <prop:Text><%[ [www] ]%></prop:Text> + </com:TActiveHyperLinkColumn> + <com:TActiveHyperLinkColumn ID="Url" + Target="_blank" + DataNavigateUrlField="Url"> + <prop:HeaderText><%[ ICS ]%></prop:HeaderText> + <prop:Text><%[ [ics] ]%></prop:Text> + </com:TActiveHyperLinkColumn> + <com:TActiveDropDownListColumn ID="Category" + DataTextField="Category.Name" + DataValueField="CategoryID" + ListValueField="ID" + ListTextField="Name"> + <prop:HeaderText><%[ Category ]%></prop:HeaderText> + </com:TActiveDropDownListColumn> + <com:TActiveTemplateColumn ID="Visible"> + <prop:HeaderText><%[ Default ]%></prop:HeaderText> + <prop:ItemTemplate> + <com:TActiveCheckBox + OnCheckedChanged="SourceTemplateControl.toggleDefaultState" + CssClass="visibilityToggle"> + <prop:Checked><%# $this->Parent->Data->Visible %></prop:Checked> + <prop:CustomData><%# $this->Parent->Data->UID %></prop:CustomData> + </com:TActiveCheckBox> + </prop:ItemTemplate> + <prop:EditItemTemplate> + <com:TCheckBox ID="CheckBox"> + <prop:Checked><%# $this->Parent->Data->Visible %></prop:Checked> + </com:TCheckBox> + </prop:EditItemTemplate> + </com:TActiveTemplateColumn> + <com:TActiveBoundColumn ID="CustomName" + DataField="CustomName"> + <prop:HeaderText><%[ Custom name ]%></prop:HeaderText> + </com:TActiveBoundColumn> + <com:TActiveBoundColumn ID="CustomUrl" + DataField="CustomUrl"> + <prop:HeaderText><%[ URL ]%></prop:HeaderText> + </com:TActiveBoundColumn> + <com:TActiveTemplateColumn ID="CustomImage"> + <prop:HeaderText><%[ Image ]%></prop:HeaderText> + <prop:ItemTemplate> + <com:TImage> + <prop:ImageUrl><%# $this->Parent->Data->CustomImageUrl %></prop:ImageUrl> + </com:TImage> + </prop:ItemTemplate> + <prop:EditItemTemplate> + <com:TActiveTextBox ID="Value"> + <prop:Text><%# $this->Parent->Data->CustomImage %></prop:Text> + </com:TActiveTextBox><br /> + <com:SafeActiveFileUpload + OnFileUpload="SourceTemplateControl.uploadRowFile"> + <prop:CustomData><%# $this->Parent->Data->UID %></prop:CustomData> + </com:SafeActiveFileUpload> + </prop:EditItemTemplate> + </com:TActiveTemplateColumn> + <com:TActiveEditCommandColumn + HeaderText=""> + <prop:EditText><%[ Edit ]%></prop:EditText> + <prop:UpdateText><%[ Save ]%></prop:UpdateText> + <prop:CancelText><%[ Cancel ]%></prop:CancelText> + </com:TActiveEditCommandColumn> + </com:TActiveDataGrid> +</com:TPanel> diff --git a/app/frontend/controls/CalendarSelection.php b/app/frontend/controls/CalendarSelection.php new file mode 100644 index 0000000..e53aa36 --- /dev/null +++ b/app/frontend/controls/CalendarSelection.php @@ -0,0 +1,17 @@ +<?php + +Prado::using('Application.controls.FacadeTemplateControl'); + +class CalendarSelection extends FacadeTemplateControl { + + public function onPreRender($param) { + parent::onPreRender($param); + if (!$this->Page->IsCallBack) { + $this->Calendars->DataSource = $this->Facade->getAll(); + $this->Calendars->dataBind(); + } + } + +} + +?> diff --git a/app/frontend/controls/CalendarSelection.tpl b/app/frontend/controls/CalendarSelection.tpl new file mode 100644 index 0000000..d6bdd83 --- /dev/null +++ b/app/frontend/controls/CalendarSelection.tpl @@ -0,0 +1,8 @@ +<com:TRepeater ID="Calendars"> + <prop:ItemTemplate> + <com:CalendarLabel> + <prop:Facade><%# $this->SourceTemplateControl->Facade %></prop:Facade> + <prop:CalendarUrl><%# $this->Data->CustomUrl %></prop:CalendarUrl> + </com:CalendarLabel> + </prop:ItemTemplate> +</com:TRepeater> diff --git a/app/frontend/controls/EventList.php b/app/frontend/controls/EventList.php new file mode 100644 index 0000000..d40e000 --- /dev/null +++ b/app/frontend/controls/EventList.php @@ -0,0 +1,59 @@ +<?php + +class EventList extends UrlBasedCalendarControl { + + private function _setDate($key, $date) { + $datetime = new DateTime($date, new DateTimeZone('UTC')); + if (!$datetime) { + throw new TInvalidDataValueException( + Prado::localize('Invalid date string: {date}', + ['date' => $date]) + ); + } + $this->setViewState($key, $datetime); + } + + public function setDateFrom($date) { + $this->_setDate('DateFrom', $date); + } + + public function getDateFrom() { + return $this->getViewState('DateFrom'); + } + + public function setDateTo($date) { + $this->_setDate('DateTo', $date); + } + + public function getDateTo() { + return $this->getViewState('DateTo'); + } + + public function setHeaderText($text) { + $this->setViewState('HeaderText', TPropertyValue::ensureString($text)); + } + + public function getHeaderText() { + return $this->getViewState('HeaderText'); + } + + public function setReverse($value) { + $this->setViewState('Reverse', TPropertyValue::ensureBoolean($value)); + } + + public function getReverse() { + return $this->getViewState('Reverse'); + } + + public function getEvents() { + return $this->getFacade()->getEventsForTimeframe( + $this->getCalendar(), + $this->getDateFrom() ?: new DateTime('0000-00-00'), + $this->getDateTo() ?: new DateTime('9999-99-99'), + $this->getReverse() ? 'DESC' : 'ASC' + ); + } + +} + +?> diff --git a/app/frontend/controls/EventList.tpl b/app/frontend/controls/EventList.tpl new file mode 100644 index 0000000..5881ce7 --- /dev/null +++ b/app/frontend/controls/EventList.tpl @@ -0,0 +1,6 @@ +<com:THeader3> + <%= $this->getHeaderText() %> +</com:THeader3> +<com:EventRepeater CalendarLinkVisible="false"> + <prop:Events><%= $this->getEvents() %></prop:Events> +</com:EventRepeater> diff --git a/app/frontend/controls/EventRepeater.php b/app/frontend/controls/EventRepeater.php new file mode 100644 index 0000000..4fb2812 --- /dev/null +++ b/app/frontend/controls/EventRepeater.php @@ -0,0 +1,25 @@ +<?php + +Prado::using('Application.web.TemplateControl'); + +class EventRepeater extends TemplateControl { + + public function setEvents($events) { + $this->Events->DataSource = $events; + $this->Events->dataBind(); + } + + public function setCalendarLinkVisible($value) { + $this->setViewState( + 'CalendarLinkVisible', + TPropertyValue::ensureBoolean($value) + ); + } + + public function getCalendarLinkVisible() { + return $this->getViewState('CalendarLinkVisible', TRUE); + } + +} + +?> diff --git a/app/frontend/controls/EventRepeater.tpl b/app/frontend/controls/EventRepeater.tpl new file mode 100644 index 0000000..69aaabf --- /dev/null +++ b/app/frontend/controls/EventRepeater.tpl @@ -0,0 +1,12 @@ +<com:TRepeater ID="Events"> + <prop:ItemTemplate> + <%# $this->Data->DateString %> + <%# $this->Data->Name %> + <com:THyperLink> + <prop:Visible><%# $this->SourceTemplateControl->CalendarLinkVisible %></prop:Visible> + <prop:Text>(<%# $this->Data->Calendar->Name %>)</prop:Text> + <prop:NavigateUrl><%# $this->Service->constructUrl('Calendar', ['calendar' => $this->Data->Calendar->Url]) %></prop:NavigateUrl> + </com:THyperLink> + <br /> + </prop:ItemTemplate> +</com:TRepeater> diff --git a/app/frontend/controls/HeaderMenu.php b/app/frontend/controls/HeaderMenu.php new file mode 100644 index 0000000..2488629 --- /dev/null +++ b/app/frontend/controls/HeaderMenu.php @@ -0,0 +1,26 @@ +<?php + +Prado::using('Application.web.TemplateControl'); + +Prado::using('System.Web.UI.ActiveControls.TActiveLinkButton'); + +class HeaderMenu extends TemplateControl { + + public function loginUser($sender, $param) { + $authModule = $this->Application->getModule('auth'); + $authModule->setReturnUrl($this->Request->RequestUri); + $this->Response->redirect( + $this->Service->ConstructUrl($authModule->LoginPage) + ); + } + + public function logoutUser($sender, $param) { + $this->Application->getModule('auth')->logout(); + $this->Response->redirect( + $this->Service->ConstructUrl(NULL) + ); + } + +} + +?> diff --git a/app/frontend/controls/HeaderMenu.tpl b/app/frontend/controls/HeaderMenu.tpl new file mode 100644 index 0000000..a760588 --- /dev/null +++ b/app/frontend/controls/HeaderMenu.tpl @@ -0,0 +1,36 @@ +<nav role="navigation"> + <com:TActiveLinkButton OnCommand="loginUser"> + <prop:Text><%[ Login ]%></prop:Text> + <prop:Visible><%= $this->User->IsGuest %></prop:Visible> + <prop:ClientSide.OnFailure>window.location.replace('<%= $this->Service->constructUrl('Login') %>')</prop:ClientSide.OnFailure> + <prop:ClientSide.OnException>window.location.replace('<%= $this->Service->constructUrl('Login') %>')</prop:ClientSide.OnException> + </com:TActiveLinkButton> + <com:THyperLink> + <prop:Text><%[ Profile ]%></prop:Text> + <prop:NavigateUrl><%= $this->Service->constructUrl('Profile') %></prop:NavigateUrl> + <prop:Visible><%= !$this->User->IsGuest %></prop:Visible> + </com:THyperLink> + <com:THyperLink> + <prop:Text><%[ Calendar list ]%></prop:Text> + <prop:NavigateUrl><%= $this->Service->constructUrl('Select') %></prop:NavigateUrl> + </com:THyperLink> + <com:TActiveLinkButton OnCommand="logoutUser"> + <com:TTranslate> + Logout ({name}) + <com:TTranslateParameter Key="name"><%= $this->User->Name %></com:TTranslateParameter> + </com:TTranslate> + <prop:Visible><%= !$this->User->IsGuest %></prop:Visible> + <prop:ClientSide.OnFailure>window.location.reload()</prop:ClientSide.OnFailure> + <prop:ClientSide.OnException>window.location.reload()</prop:ClientSide.OnException> + </com:TActiveLinkButton> + <com:THyperLink> + <prop:Text><%[ New user ]%></prop:Text> + <prop:NavigateUrl><%= $this->Service->constructUrl('Signup') %></prop:NavigateUrl> + <prop:Visible><%= $this->User->getIsAdmin() %></prop:Visible> + </com:THyperLink> + <com:THyperLink> + <prop:Text><%[ Admin calendars ]%></prop:Text> + <prop:NavigateUrl><%= $this->Service->constructUrl('Admin') %></prop:NavigateUrl> + <prop:Visible><%= $this->User->getIsAdmin() %></prop:Visible> + </com:THyperLink> +</nav> diff --git a/app/frontend/controls/LoginBox.php b/app/frontend/controls/LoginBox.php new file mode 100644 index 0000000..1136a79 --- /dev/null +++ b/app/frontend/controls/LoginBox.php @@ -0,0 +1,39 @@ +<?php + +Prado::using('Application.web.TemplateControl'); + +class LoginBox extends TemplateControl { + + public function onInit($param) { + parent::onInit($param); + if (!$this->Page->IsPostBack && !$this->User->IsGuest) { + $this->_afterLoginRedirect(); + } + } + + private function _afterLoginRedirect() { + $authModule = $this->Application->getModule('auth'); + $redirUrl = $authModule->ReturnUrl; + if (!$redirUrl + || $redirUrl == $this->Service->constructUrl($authModule->LoginPage)) { + $redirUrl = $this->Service->constructUrl(NULL); + } + $this->Response->redirect($redirUrl); + } + + public function loginUser($sender, $param) { + if ($this->Page->IsValid) { + $this->_afterLoginRedirect(); + } + } + + public function validatePassword($sender, $param) { + $param->IsValid = $this->Application->getModule('auth')->login( + $this->Login->Text, + $this->Password->Text + ); + } + +} + +?> diff --git a/app/frontend/controls/LoginBox.tpl b/app/frontend/controls/LoginBox.tpl new file mode 100644 index 0000000..070ab86 --- /dev/null +++ b/app/frontend/controls/LoginBox.tpl @@ -0,0 +1,33 @@ +<%[ Username: ]%> +<com:TTextBox ID="Login" + ValidationGroup="LoginGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="Login" + Display="Dynamic" + ValidationGroup="LoginGroup"> + <prop:ErrorMessage><%[ Username cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<br /> +<%[ Password: ]%> +<com:TTextBox ID="Password" + TextMode="Password" + ValidationGroup="LoginGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="Password" + Display="Dynamic" + ValidationGroup="LoginGroup"> + <prop:ErrorMessage><%[ Password cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<com:TCustomValidator + ControlToValidate="Password" + OnServerValidate="validatePassword" + Display="Dynamic" + ValidationGroup="LoginGroup"> + <prop:ErrorMessage><%[ Username and password don't match ]%></prop:ErrorMessage> +</com:TCustomValidator> +<br /> +<com:TButton + OnCommand="loginUser" + ValidationGroup="LoginGroup"> + <prop:Text><%[ Login ]%></prop:Text> +</com:TButton> diff --git a/app/frontend/controls/PasswordChange.php b/app/frontend/controls/PasswordChange.php new file mode 100644 index 0000000..45ce656 --- /dev/null +++ b/app/frontend/controls/PasswordChange.php @@ -0,0 +1,44 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); + +Prado::using('Application.user.DbUser'); +Prado::using('Application.facades.UserFacade'); + +class PasswordChange extends FacadeTemplateControl { + + public function getUserToChange() { + return $this->getControlState('user'); + } + + public function setUserToChange(DbUser $user) { + if ($user->IsGuest && !$this->Page->IsCallBack) { + throw new TInvalidDataValueException( + Prado::localize( + 'Password change impossible for guest user' + ) + ); + } + $this->setControlState('user', $user); + } + + public function checkPassword($sender, $param) { + $param->IsValid = $this->getFacade()->verifyUserPassword( + $this->Password->Text, $this->UserToChange + ); + } + + public function changePassword($sender, $param) { + $this->SuccessMessage->Visible = FALSE; + if ($this->Page->IsValid) { + $this->getFacade()->changePassword( + $this->UserToChange, + $this->NewPassword->Text + ); + $this->SuccessMessage->Visible = TRUE; + } + } + +} + +?> diff --git a/app/frontend/controls/PasswordChange.tpl b/app/frontend/controls/PasswordChange.tpl new file mode 100644 index 0000000..4eb9a8e --- /dev/null +++ b/app/frontend/controls/PasswordChange.tpl @@ -0,0 +1,59 @@ +<%[ Change password ]%><br /> +<%[ Current password: ]%> +<com:TTextBox ID="Password" + TextMode="Password" + ValidationGroup="ChangePasswordGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="Password" + Display="Dynamic" + ValidationGroup="ChangePasswordGroup"> + <prop:ErrorMessage><%[ Current password cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<com:TCustomValidator + ControlToValidate="Password" + OnServerValidate="checkPassword" + Display="Dynamic" + ValidationGroup="ChangePasswordGroup"> + <prop:ErrorMessage><%[ Password is incorrect ]%></prop:ErrorMessage> +</com:TCustomValidator> +<br /> +<%[ New password: ]%> +<com:TTextBox ID="NewPassword" + TextMode="Password" + ValidationGroup="ChangePasswordGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="NewPassword" + Display="Dynamic" + ValidationGroup="ChangePasswordGroup"> + <prop:ErrorMessage><%[ New password cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<br /> +<%[ Repeat password: ]%> +<com:TTextBox ID="ReNewPassword" + TextMode="Password" + ValidationGroup="ChangePasswordGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="ReNewPassword" + Display="Dynamic" + ValidationGroup="ChangePasswordGroup"> + <prop:ErrorMessage><%[ New password cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<com:TCompareValidator + ControlToValidate="ReNewPassword" + ControlToCompare="NewPassword" + DataType="String" + Operator="Equal" + Display="Dynamic" + ValidationGroup="ChangePasswordGroup"> + <prop:ErrorMessage><%[ Passwords don't match ]%></prop:ErrorMessage> +</com:TCompareValidator> +<br /> +<com:TButton + OnCommand="changePassword" + ValidationGroup="ChangePasswordGroup"> + <prop:Text><%[ Change password ]%></prop:Text> +</com:TButton> +<com:TLabel ID="SuccessMessage" + Visible="false"> + <prop:Text><%[ Your password has been changed ]%></prop:Text> +</com:TLabel> diff --git a/app/frontend/controls/RegistrationForm.php b/app/frontend/controls/RegistrationForm.php new file mode 100644 index 0000000..46494e3 --- /dev/null +++ b/app/frontend/controls/RegistrationForm.php @@ -0,0 +1,28 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); + +Prado::using('Application.facades.UserFacade'); + +class RegistrationForm extends FacadeTemplateControl { + + public function checkUsername($sender, $param) { + $param->IsValid = $this->getFacade()->checkForUsername($this->Login->SafeText); + } + + public function registerUser($sender, $param) { + if ($this->Page->IsValid) { + $this->getFacade()->registerUser( + $this->Login->SafeText, + $this->Password->Text, + $this->Admin->Checked + ); + $this->Response->redirect( + $this->Service->constructUrl(NULL) + ); + } + } + +} + +?> diff --git a/app/frontend/controls/RegistrationForm.tpl b/app/frontend/controls/RegistrationForm.tpl new file mode 100644 index 0000000..9defe54 --- /dev/null +++ b/app/frontend/controls/RegistrationForm.tpl @@ -0,0 +1,66 @@ +<%[ Username: ]%> +<com:TTextBox ID="Login" + ValidationGroup="SignupGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="Login" + Display="Dynamic" + ValidationGroup="SignupGroup"> + <prop:ErrorMessage><%[ Username cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<com:TRegularExpressionValidator + ControlToValidate="Login" + RegularExpression="[a-zA-Z0-9_]{6,255}" + Display="Dynamic" + ValidationGroup="SignupGroup"> + <prop:ErrorMessage><%[ Username must contain 6-255 characters, all Latin alphanumeric or underscore ]%></prop:ErrorMessage> +</com:TRegularExpressionValidator> +<com:TCustomValidator + ControlToValidate="Login" + OnServerValidate="checkUsername" + Display="Dynamic" + ValidationGroup="SignupGroup"> + <prop:ErrorMessage><%[ Username already exists ]%></prop:ErrorMessage> +</com:TCustomValidator> +<br /> +<%[ Password: ]%> +<com:TTextBox ID="Password" + TextMode="Password" + ValidationGroup="SignupGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="Password" + Display="Dynamic" + ValidationGroup="SignupGroup"> + <prop:ErrorMessage><%[ Password cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<br /> +<%[ Repeat password: ]%> +<com:TTextBox ID="RePassword" + TextMode="Password" + ValidationGroup="SignupGroup" /> +<com:TRequiredFieldValidator + ControlToValidate="RePassword" + Display="Dynamic" + ValidationGroup="SignupGroup"> + <prop:ErrorMessage><%[ Password cannot be empty ]%></prop:ErrorMessage> +</com:TRequiredFieldValidator> +<com:TCompareValidator + ControlToValidate="RePassword" + ControlToCompare="Password" + DataType="String" + Operator="Equal" + Display="Dynamic" + ValidationGroup="SignupGroup"> + <prop:ErrorMessage><%[ Passwords don't match ]%></prop:ErrorMessage> +</com:TCompareValidator> +<br /> +<%[ Admin: ]%> +<com:TCheckBox ID="Admin" + ValidationGroup="SignupGroup" /> +<br /> +<com:TButton + OnCommand="registerUser" + ValidationGroup="SignupGroup"> + <prop:Text><%[ Create ]%></prop:Text> +</com:TButton> +<com:TValidationSummary + ValidationGroup="SignupGroup" /> diff --git a/app/frontend/controls/TimezoneSelect.php b/app/frontend/controls/TimezoneSelect.php new file mode 100644 index 0000000..25af453 --- /dev/null +++ b/app/frontend/controls/TimezoneSelect.php @@ -0,0 +1,58 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); + +Prado::using('Application.user.DbUser'); +Prado::using('Application.facades.UserFacade'); + +Prado::using('Application.dto.TimezoneDTO'); + +class TimezoneSelect extends FacadeTemplateControl { + + public function getUserToChange() { + return $this->getControlState('user'); + } + + public function setUserToChange(DbUser $user) { + if ($user->IsGuest && !$this->Page->IsCallBack) { + throw new TInvalidDataValueException( + Prado::localize( + 'Timezone preference change impossible for guest user' + ) + ); + } + $this->setControlState('user', $user); + } + + public function onPreRender($param) { + parent::onPreRender($param); + $this->Timezones->DataSource = $this->_getTimezones(); + $this->Timezones->DataValueField = 'Name'; + $this->Timezones->DataTextField = 'Label'; + $this->Timezones->dataBind(); + $this->Timezones->setSelectedValue( + $this->getFacade()->getTimezonePreference($this->UserToChange)->Name + ); + } + + public function saveTimezone($sender, $param) { + $this->getFacade()->setTimezonePreference( + $this->UserToChange, + $this->Timezones->SelectedValue + ); + } + + private function _getTimezones() { + $timezones = array_map( + function($tz) { + return new TimezoneDTO($tz); + }, + DateTimeZone::listIdentifiers() + ); + usort($timezones, ['TimezoneDTO', '__compare']); + return $timezones; + } + +} + +?> diff --git a/app/frontend/controls/TimezoneSelect.tpl b/app/frontend/controls/TimezoneSelect.tpl new file mode 100644 index 0000000..ee703d6 --- /dev/null +++ b/app/frontend/controls/TimezoneSelect.tpl @@ -0,0 +1,5 @@ +<com:TDropDownList ID="Timezones" /> +<com:TButton + OnCommand="saveTimezone"> + <prop:Text><%[ Save timezone ]%></prop:Text> +</com:TButton> diff --git a/app/frontend/controls/UpcomingEvents.php b/app/frontend/controls/UpcomingEvents.php new file mode 100644 index 0000000..27fc723 --- /dev/null +++ b/app/frontend/controls/UpcomingEvents.php @@ -0,0 +1,33 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); +Prado::using('Application.user.DbUser'); +Prado::using('Application.facades.EventFacade'); + +class UpcomingEvents extends FacadeTemplateControl { + + public function getUserToDisplay() { + return $this->getControlState('user'); + } + + public function setUserToDisplay(DbUser $user) { + $this->setControlState('user', $user); + } + + public function getEvents() { + return $this->_getEventsForUser($this->UserToDisplay); + } + + private function _getEventsForUser(DbUser $user) { + $utc = new DateTimeZone('UTC'); + $dateFrom = new DateTime('now', $utc); + $dateTo = new DateTime('+7 days', $utc); + return $this->getFacade()->getTimeframeListForUser( + $user, + $dateFrom, $dateTo + ); + } + +} + +?> diff --git a/app/frontend/controls/UpcomingEvents.tpl b/app/frontend/controls/UpcomingEvents.tpl new file mode 100644 index 0000000..ac5f60c --- /dev/null +++ b/app/frontend/controls/UpcomingEvents.tpl @@ -0,0 +1,5 @@ +<%[ Upcoming events: ]%> +<br /> +<com:EventRepeater ID="Events"> + <prop:Events><%= $this->getEvents() %></prop:Events> +</com:EventRepeater> diff --git a/app/frontend/controls/UrlBasedCalendarControl.php b/app/frontend/controls/UrlBasedCalendarControl.php new file mode 100644 index 0000000..a5be82e --- /dev/null +++ b/app/frontend/controls/UrlBasedCalendarControl.php @@ -0,0 +1,40 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); +Prado::using('Application.facades.CalendarFacade'); + +class UrlBasedCalendarControl extends FacadeTemplateControl { + + public function setCalendarUrl(string $url = NULL) { + if ($url) { + $calendar = $this->getFacade()->resolveUrl($url); + if ($calendar) { + $this->setControlState('Calendar', $calendar); + return; + } + } + if ($this->getRaiseException()) { + throw new THttpException( + 404, + Prado::localize('Page not found') + ); + } else { + $this->Visible = FALSE; + } + } + + public function getCalendar() { + return $this->getControlState('Calendar'); + } + + public function setRaiseException($value) { + $this->setControlState('RaiseException', TPropertyValue::ensureBoolean($value)); + } + + public function getRaiseException() { + return $this->getControlState('RaiseException', FALSE); + } + +} + +?> diff --git a/app/frontend/controls/UserSelection.php b/app/frontend/controls/UserSelection.php new file mode 100644 index 0000000..233f0bf --- /dev/null +++ b/app/frontend/controls/UserSelection.php @@ -0,0 +1,45 @@ +<?php + +Prado::using('Application.web.FacadeTemplateControl'); +Prado::using('Application.user.DbUser'); +Prado::using('Application.facades.CalendarFacade'); + +class UserSelection extends FacadeTemplateControl { + + public function getUserToDisplay() { + return $this->getControlState('user'); + } + + public function setUserToDisplay(DbUser $user = NULL) { + $this->setControlState('user', $user); + } + + public function onPreRender($param) { + parent::onPreRender($param); + $this->Categories->setDataSource( + $this->_getUserSelection($this->UserToDisplay) + ); + $this->Categories->dataBind(); + } + + public function categoryDataBind($sender, $param) { + $param->Item->Calendars->setDataSource($param->Item->Data->Calendars); + $param->Item->Calendars->dataBind(); + } + + public function removeFromSelection($sender, $param) { + if (!$this->UserToDisplay->IsGuest) { + return $this->getFacade()->removeFromPreference( + $this->UserToDisplay, + $param->CommandParameter + ); + } + } + + private function _getUserSelection(DbUser $user) { + return $this->getFacade()->getPreferenceList($user); + } + +} + +?> diff --git a/app/frontend/controls/UserSelection.tpl b/app/frontend/controls/UserSelection.tpl new file mode 100644 index 0000000..8d20365 --- /dev/null +++ b/app/frontend/controls/UserSelection.tpl @@ -0,0 +1,29 @@ +<%[ Selected calendars: ]%> +<br /> +<com:TRepeater ID="Categories" OnItemDataBound="categoryDataBind"> + <prop:ItemTemplate> + <%# $this->Data->Name %><br /> + <com:TRepeater ID="Calendars"> + <prop:ItemTemplate> + <com:TLinkButton + Text="[X]" + OnCommand="SourceTemplateControl.removeFromSelection"> + <prop:CommandParameter><%# $this->Data->ID %></prop:CommandParameter> + <prop:Visible><%# !$this->SourceTemplateControl->UserToDisplay->IsGuest %></prop:Visible> + </com:TLinkButton> + <%# $this->Data->Name %> + <com:THyperLink> + <prop:Text><%[ (details) ]%></prop:Text> + <prop:NavigateUrl><%# $this->Service->constructUrl('Calendar', ['calendar' => $this->Data->Url]) %></prop:NavigateUrl> + </com:THyperLink> + <com:THyperLink + Text="(www)" + Target="_blank"> + <prop:NavigateUrl><%# $this->Data->Website %></prop:NavigateUrl> + </com:THyperLink> + <br /> + </prop:ItemTemplate> + </com:TRepeater> + <br /> + </prop:ItemTemplate> +</com:TRepeater> diff --git a/app/frontend/controls/config.xml b/app/frontend/controls/config.xml new file mode 100644 index 0000000..61d7e5b --- /dev/null +++ b/app/frontend/controls/config.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <paths> + <using namespace="Application.controls.*" /> + </paths> +</configuration> diff --git a/app/frontend/controls/scripts/AddToFilter.js b/app/frontend/controls/scripts/AddToFilter.js new file mode 100644 index 0000000..e6d7b39 --- /dev/null +++ b/app/frontend/controls/scripts/AddToFilter.js @@ -0,0 +1,5 @@ +$(document).on('ready', function() { + $('input.addToFilterBox').on('change', function() { + $(this).attr('disabled', true); + }); +}); diff --git a/app/frontend/controls/scripts/CalendarGroupFilter.js b/app/frontend/controls/scripts/CalendarGroupFilter.js new file mode 100644 index 0000000..e6c1d73 --- /dev/null +++ b/app/frontend/controls/scripts/CalendarGroupFilter.js @@ -0,0 +1,29 @@ +$(document).on('ready', function() { + var selectBoxes = $('.selectGroup input.box'); + var selectAllBox = $('.selectAllGroups input.box'); + selectBoxes.on('change', function() { + var groups = []; + var allSelected = true; + $('.selectGroup input.box').each(function() { + var box = $(this); + if (box.is(':checked')) { + groups.push(box.val()); + } else { + allSelected = false; + } + }); + if (allSelected) { + selectAllBox.prop('checked', true); + } else { + selectAllBox.removeAttr('checked'); + } + $(document).trigger('Application.calendarGroupFilterChanged', {groups: groups}); + }); + selectAllBox.on('change', function() { + if ($(this).is(':checked')) { + selectBoxes.prop('checked', true).trigger('change'); + } else { + selectBoxes.removeAttr('checked').trigger('change'); + } + }); +}); diff --git a/app/frontend/controls/scripts/CalendarLabel.js b/app/frontend/controls/scripts/CalendarLabel.js new file mode 100644 index 0000000..8193e56 --- /dev/null +++ b/app/frontend/controls/scripts/CalendarLabel.js @@ -0,0 +1,11 @@ +$(document).on('Application.calendarGroupFilterChanged', function(event, args) { + var selectedGroups = args.groups || []; + $('.calendar').each(function() { + var label = $(this); + if (selectedGroups.indexOf(label.attr('data-group')) >= 0) { + label.show(); + } else { + label.hide(); + } + }); +}); diff --git a/app/frontend/controls/scripts/CalendarScaffold.js b/app/frontend/controls/scripts/CalendarScaffold.js new file mode 100644 index 0000000..d4b8ec5 --- /dev/null +++ b/app/frontend/controls/scripts/CalendarScaffold.js @@ -0,0 +1,8 @@ +$('body').on( + 'click', + 'main .calendarScaffold tbody a[href^="javascript:;//"], main .calendarScaffold tbody input.visibilityToggle', + function(e) { + var loader = $('<div>').addClass('calendarScaffoldLoader'); + $('main .calendarScaffold div[id$="_Container"]').append(loader); + } +); diff --git a/app/frontend/controls/styles/CalendarGrid.css b/app/frontend/controls/styles/CalendarGrid.css new file mode 100644 index 0000000..4710993 --- /dev/null +++ b/app/frontend/controls/styles/CalendarGrid.css @@ -0,0 +1,16 @@ +div.gridWeek { + clear: both; + display: flex; + flex-flow: row nowrap; +} +div.gridDay { + width: 14%; + min-height: 8em; + flex: 1 1 auto; +} +div.gridEvent, div.gridItem { height: 1.5em; padding: 0.3em 0.5em; margin: 0.1em 0; } +div.gridEvent { overflow: hidden; white-space: nowrap; background: #ddd } +div.gridEvent.beginDate { border-top-left-radius: 1.5em; + border-bottom-left-radius: 1.5em } +div.gridEvent.endDate { border-top-right-radius: 1.5em; + border-bottom-right-radius: 1.5em } diff --git a/app/frontend/controls/styles/CalendarScaffold.css b/app/frontend/controls/styles/CalendarScaffold.css new file mode 100644 index 0000000..071f672 --- /dev/null +++ b/app/frontend/controls/styles/CalendarScaffold.css @@ -0,0 +1,11 @@ +.calendarScaffold { + position: relative; +} +.calendarScaffold .calendarScaffoldLoader { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url(preloader.gif) no-repeat center 5% rgba(255,255,255,0.8); +} |