diff --git a/Resources/templates/responsive/creator/index.php b/Resources/templates/responsive/creator/index.php new file mode 100644 index 0000000000..b9e4633da9 --- /dev/null +++ b/Resources/templates/responsive/creator/index.php @@ -0,0 +1,42 @@ +layout("layout", [ + 'bodyClass' => 'project creator', +]); + +$permanentProject = $this->permanentProject; +$listOfProjects = $this->listOfProjects; +?> + +section('head'); ?> + insert('creator/partials/styles') ?> +append(); ?> + +section('content'); ?> + +
+
+
+

markdown($this->ee($permanentProject->name)) ?>

+
user->name ?>
+
+ +
+
+ insert('project/partials/media', ['project' => $permanentProject ]) ?> +
+
+ + insert('creator/partials/share_social') ?> + + insert('creator/partials/channel', ['project' => $permanentProject]) ?> + + insert('creator/partials/subscriptions', ['project' => $permanentProject, 'subscriptions' => $permanentProject->getRewardsOrderBySubscribable()]) ?> + + insert('creator/partials/posts', ['project' => $permanentProject, 'subscriptions' => $permanentProject->getSubscribableRewards()]) ?> +
+
+ +append(); ?> + +section('footer'); ?> + insert('creator/partials/javascript') ?> +append(); ?> diff --git a/Resources/templates/responsive/creator/partials/channel.php b/Resources/templates/responsive/creator/partials/channel.php new file mode 100644 index 0000000000..60056516bf --- /dev/null +++ b/Resources/templates/responsive/creator/partials/channel.php @@ -0,0 +1,18 @@ +channels; + if (!$channels) + return; +?> + +
+

t('regular-channels') ?>

+
+ channels as $channel): ?> + + +
+
diff --git a/Resources/templates/responsive/creator/partials/post_item.php b/Resources/templates/responsive/creator/partials/post_item.php new file mode 100644 index 0000000000..b9982ca96d --- /dev/null +++ b/Resources/templates/responsive/creator/partials/post_item.php @@ -0,0 +1,25 @@ +post; + $user = $this->user; +?> + +
+ image): ?> +
+ <?= $post->title ?> +
+ + +
+ +
+
+ <?= $user->name ?> name ?> +
+ post->date) ?> +
+
+
diff --git a/Resources/templates/responsive/creator/partials/posts.php b/Resources/templates/responsive/creator/partials/posts.php new file mode 100644 index 0000000000..675efa4a9c --- /dev/null +++ b/Resources/templates/responsive/creator/partials/posts.php @@ -0,0 +1,15 @@ +posts; +if (empty($posts)) + return; +?> + +
+

t('regular-posts') ?>

+ +
+ + insert('creator/partials/post_item', ['post' => $post]); ?> + +
+
diff --git a/Resources/templates/responsive/creator/partials/projects.php b/Resources/templates/responsive/creator/partials/projects.php new file mode 100644 index 0000000000..fefb797b22 --- /dev/null +++ b/Resources/templates/responsive/creator/partials/projects.php @@ -0,0 +1,15 @@ +projects; +if (empty($projects)) + return; +?> + +
+

t('regular-projects') ?>

+ +
+ listOfProjects as $project) : ?> + insert('project/widgets/normal', ['project' => $project]) ?> + +
+
diff --git a/Resources/templates/responsive/creator/partials/share_social.php b/Resources/templates/responsive/creator/partials/share_social.php new file mode 100644 index 0000000000..7b0e0c82a9 --- /dev/null +++ b/Resources/templates/responsive/creator/partials/share_social.php @@ -0,0 +1,25 @@ +get_url() . '/creator/' . $this->user->id; + +$user_twitter = str_replace( + [ + 'https://', + 'http://', + 'www.', + 'twitter.com/', + '#!/', + '@' + ], '', $this->user->twitter); +$author_share = !empty($user_twitter) ? ' '.$this->text('regular-by').' @'.$user_twitter.' ' : ''; +$share_title = $author_share; + +$facebook_url = 'http://facebook.com/sharer.php?u=' . urlencode($share_url) . '&t=' . urlencode($share_title); +$twitter_url = 'http://twitter.com/intent/tweet?text=' . urlencode($share_title . ': ' . $share_url . ' #Goteo'); +?> +
+ +
diff --git a/Resources/templates/responsive/creator/partials/subscription_item.php b/Resources/templates/responsive/creator/partials/subscription_item.php new file mode 100644 index 0000000000..d9386a7165 --- /dev/null +++ b/Resources/templates/responsive/creator/partials/subscription_item.php @@ -0,0 +1,26 @@ +project; +$subscription = $this->subscription; +?> + +
+
+

reward ?>

+
+
+
+ amount) ?> +
+ +

+ markdown($subscription->description) ?> +

+
+ +
diff --git a/Resources/templates/responsive/creator/partials/subscriptions.php b/Resources/templates/responsive/creator/partials/subscriptions.php new file mode 100644 index 0000000000..fa54a629df --- /dev/null +++ b/Resources/templates/responsive/creator/partials/subscriptions.php @@ -0,0 +1,15 @@ +subscriptions; +if (empty($subscriptions)) + return; +?> + +
+

t('regular-subscriptions') ?>

+ +
+ + insert('creator/partials/subscription_item', ['subscription' => $subscription]); ?> + +
+
diff --git a/public/assets/js/creator/creator.js b/public/assets/js/creator/creator.js new file mode 100644 index 0000000000..1e33055ed5 --- /dev/null +++ b/public/assets/js/creator/creator.js @@ -0,0 +1,79 @@ +/* +@licstart The following is the entire license notice for the +JavaScript code in this page. + +Copyright (C) 2010 Goteo Foundation + +The JavaScript code in this page is free software: you can +redistribute it and/or modify it under the terms of the GNU +General Public License (GNU GPL) as published by the Free Software +Foundation, either version 3 of the License, or (at your option) +any later version. The code is distributed WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + +As additional permission under GNU GPL version 3 section 7, you +may distribute non-source (e.g., minimized or compacted) forms of +that code without the copy of the GNU GPL normally required by +section 4, provided you include this license notice and a URL +through which recipients can access the Corresponding Source. + + +@licend The above is the entire license notice +for the JavaScript code in this page. +*/ + +$(function(){ + $('.slider-subscriptions').slick({ + dots: true, + infinite: true, + slidesToShow: 3, + slidesToScroll: 1, + arrows: true, + cssEase: 'linear', + prevArrow: '
Prev
', + nextArrow: '
Next
', + responsive: [ + { + breakpoint: 992, + settings: { + slidesToShow: 2.5, + arrows:false + } + }, + { + breakpoint: 768, + settings: { + slidesToShow: 1.5, + } + } + ] + }); + + $('.slider-channel').slick({ + dots: true, + infinite: true, + slidesToShow: 3, + slidesToScroll: 1, + arrows: true, + cssEase: 'linear', + prevArrow: '
Prev
', + nextArrow: '
Next
', + responsive: [ + { + breakpoint: 992, + settings: { + slidesToShow: 2.5, + arrows:false + } + }, + { + breakpoint: 768, + settings: { + slidesToShow: 1.5, + } + } + ] + }); + +}); diff --git a/public/assets/sass/creator.scss b/public/assets/sass/creator.scss new file mode 100644 index 0000000000..9a7ee0bb5d --- /dev/null +++ b/public/assets/sass/creator.scss @@ -0,0 +1,259 @@ +@import 'variables'; + +body.creator { + + section { + h2 { + font-weight: bold; + text-align: center; + text-transform: uppercase; + } + } + + section.subscriptions { + + .slider-subscriptions { + .slick-slide { + margin: 1em; + } + + .custom-left-arrow{ + top: 34%; + position: absolute; + font-size: $font-size*5; + left: -2.4%; + color: $background-light-green; + cursor: pointer; + } + .custom-right-arrow{ + top: 34%; + position: absolute; + font-size: $font-size*5; + right: -4%; + color: $background-light-green; + cursor: pointer; + } + } + } + + article.subscription { + display: grid; + grid-template-rows: auto auto auto; + background-color: white; + border: 1px solid $color-light-grey; + border-radius: 1rem; + width: 300px; + padding: 2rem; + font-size: 1.5rem; + transition: all 0.5s ease-in-out; + + &:only-child { + grid-column-start: 1; + } + + .card-header { + h2 { + overflow: hidden; + text-overflow: ellipsis; + } + } + + .card-body { + height: 20rem; + overflow: hidden; + text-overflow: ellipsis; + padding-bottom: 1rem; + + .amount-box { + color: $color-dark-green; + font-size: 40px; + font-weight: bold; + } + + img { + width: 100%; + } + + &:hover { + height: fit-content; + min-height: 20rem; + } + } + + &:hover { + border: $background-light-green 2px solid; + } + } + + section.channel { + article.channel { + margin-top: unset; + + } + } + + section.posts { + .post-grid { + display: grid; + grid-template-columns: 1fr 4fr 1fr; + + gap: 1rem; + padding: 1rem; + margin-top: 2.5rem; + + article.post { + display: grid; + grid-template-columns: 1fr; + grid-column-start: 2; + margin: 20px; + border: 1px solid #ddd; + border-radius: 8px; + + h2 { + overflow: hidden; + text-overflow: ellipsis; + } + + &:has(.post-image) { + grid-template-columns: 1fr 2fr; + } + + .post-image { + width: 100%; + + img { + object-fit: cover; + height: 100%; + display: block; + } + } + + .card-body { + display: grid; + grid-template-rows: 2fr 1fr; + + .card-info { + margin: 1em; + text-align: center; + + h2 { + text-wrap: balance; + + a { + color: inherit; + } + } + + p { + display: -webkit-box; + text-overflow: ellipsis; + line-clamp: 2; + } + } + .card-author-info { + padding: 1em; + display: flex; + align-items: center; + background-color: $background-light-green; + justify-content: space-between; + color: $color-white; + + .author-image { + margin-right: 10px; + + img { + width: 30px; + height: 30px; + border-radius: 50%; + object-fit: cover; + } + } + } + } + } + } + } + + section.projects { + .project-grid { + display: grid; + grid-template-columns: 1fr; + justify-items: center; + grid-column-gap: 3em; + grid-row-gap: 3em; + + &:has(> :last-child:nth-child(2)) { + grid-template-columns: repeat(2, 1fr); + + .project-widget:first-child { + justify-self:right; + } + .project-widget:last-child { + justify-self:left; + } + } + + padding: 1rem; + margin-top: 2.5rem; + } + } + + section.share-social { + display:flex; + justify-content: center; + margin-top: 2em; + color: $primary-color; + } + + @media (max-width: $breakpoint-lg) { + section.projects .project-grid, section.subscriptions .subscription-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + + section.channel { + article.channel { + display: grid; + justify-content: center; + + a { + img { + border-radius: 100%; + object-fit: contain; + } + } + } + } + + @media (max-width: $breakpoint-md) { + section.projects .project-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + section.subscriptions .subscription-grid { + &:has(> :last-child:nth-child(2)) { + grid-template-columns: repeat(2, 1fr); + grid-column-gap: 3em; + + article.subscription:first-child { + justify-self:right; + } + article.subscription:last-child { + justify-self:left; + } + } + } + } + + @media (max-width: $breakpoint-sm) { + section.projects div.project-grid, section.subscriptions .subscription-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + + @media (max-width: $breakpoint-xs) { + section.projects div.project-grid, section.subscriptions .subscription-grid { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + } +} diff --git a/public/templates/responsive/creator/partials/javascript.php b/public/templates/responsive/creator/partials/javascript.php new file mode 100644 index 0000000000..1eb1c3d429 --- /dev/null +++ b/public/templates/responsive/creator/partials/javascript.php @@ -0,0 +1,5 @@ + + + + + diff --git a/public/templates/responsive/creator/partials/styles.php b/public/templates/responsive/creator/partials/styles.php new file mode 100644 index 0000000000..6a2bb74190 --- /dev/null +++ b/public/templates/responsive/creator/partials/styles.php @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Goteo/Controller/AdminController.php b/src/Goteo/Controller/AdminController.php index 30754baf14..2b8b1b85ac 100644 --- a/src/Goteo/Controller/AdminController.php +++ b/src/Goteo/Controller/AdminController.php @@ -16,6 +16,7 @@ use Goteo\Application\Message; use Goteo\Application\Session; use Goteo\Application\View; +use Goteo\Controller\Admin\AdminControllerInterface; use Goteo\Core\Controller; use Goteo\Library\Feed; use Goteo\Library\Text; @@ -135,17 +136,18 @@ public function routingAction(Request $request, $id, $uri = '') { throw new NotFoundHttpException("Admin module [$id] not found"); } - public static function createAdminSidebar (User $user, $uri = '') { - + public static function createAdminSidebar(User $user, $uri = '') + { $prefix = '/admin'; foreach (static::$subcontrollers as $id => $class) { - if(in_array('Goteo\Controller\Admin\AdminControllerInterface', class_implements($class))) { + if(in_array(AdminControllerInterface::class, class_implements($class))) { if(!$class::isAllowed($user)) continue; $label = $class::getLabel('html'); - $cls = strpos($label, ' $route) { @@ -153,21 +155,35 @@ public static function createAdminSidebar (User $user, $uri = '') { if(!is_array($route)) { $route = ['text' => $route, 'link' => $link]; } - $c = $route['class'] ? $route['class'] : (strpos($route['text'], ' $route['text'], 'link' => $prefix . $route['link'], 'id' => $route['id'], 'class' => $c]; + $paths[] = [ + 'id' => $route['id'], + 'text' => $route['text'], + 'link' => $prefix . $route['link'], + 'class' => $route['class'] + ? $route['class'] + : (strpos($route['text'], ' $label, 'link' => "$prefix/$id", 'id' => "/$id", 'class' => $cls]; - $modules[$group ? $group : 'main'][] = $init_route; + $modules[$group ? $group : 'main'][] = [ + 'id' => "/$id", + 'text' => $label, + 'link' => "$prefix/$id", + 'class' => strpos($label, ' $ms) { @@ -178,9 +194,17 @@ public static function createAdminSidebar (User $user, $uri = '') { } } } - $modules[$group][] = ['text' => $class::getLabel(), 'link' => "$prefix/$id", 'id' => "/$id", 'class' => 'nopadding']; + + $modules[$group][] = [ + 'text' => $class::getLabel(), + 'link' => "$prefix/$id", + 'id' => "/$id", + 'class' => 'nopadding' + ]; + } } + // group the modules that don't define a custom menu $index = 1; $zone = ''; diff --git a/src/Goteo/Controller/CreatorController.php b/src/Goteo/Controller/CreatorController.php new file mode 100644 index 0000000000..5a525f181f --- /dev/null +++ b/src/Goteo/Controller/CreatorController.php @@ -0,0 +1,79 @@ +getMessage()); + return $this->redirect('/'); + } + + if (!$user instanceof User) { + Message::error(Text::get('fatal-error-user')); + return $this->redirect('/'); + } + + if (!$user->hasRole('creator')) + return $this->redirectToRoute('user-profile', ['id' => $user->id]); + + /** @var Project $permanentProject */ + $permanentProject = current(Project::getList(['type_of_campaign' => Project\Conf::TYPE_PERMANENT, 'owner' => $user->id, 'status' => [Project::STATUS_IN_CAMPAIGN]])); + $listOfProjects = Project::getList(['type_of_campaign' => Project\Conf::TYPE_CAMPAIGN, 'owner' => $user->id, 'status' => [Project::STATUS_IN_CAMPAIGN, Project::STATUS_FUNDED, Project::STATUS_FULFILLED]]); + $posts = Post::getList(['author' => $user->id, 'show' => 'published']); + if (empty($permanentProject)) + return $this->redirect($this->generateUrl('user-profile', ['id' => $user->id])); + + $channels = $this->getChannelsForProject($permanentProject); + + return $this->renderFoilTemplate('creator/index', [ + 'user' => $user, + 'permanentProject' => $permanentProject, + 'listOfProjects' => $listOfProjects, + 'posts' => $posts, + 'channels' => $channels + ]); + } + + + /** + * @return Node[] + */ + private function getChannelsForProject(Project $project): array + { + $channels = []; + + $nodeProjectList = NodeProject::getList([ + 'project' => $project->id + ]); + + foreach($nodeProjectList as $node) { + $channels[] = Node::get($node->node_id); + } + + return $channels; + } +} diff --git a/src/Goteo/Model/Project.php b/src/Goteo/Model/Project.php index b4590bd7d3..a6d1cc817d 100644 --- a/src/Goteo/Model/Project.php +++ b/src/Goteo/Model/Project.php @@ -753,6 +753,16 @@ public function getIndividualRewards($lang = null) { return $this->individual_rewards; } + public function getSubscribableRewards(): array + { + return Reward::getSubscribableRewardsForProject($this); + } + + public function getRewardsOrderBySubscribable(): array + { + return Reward::getRewardsOrderBySubscribableForProject($this); + } + public function getSupports($lang = null) { if(!$this->supports) { // colaboraciones diff --git a/src/Goteo/Model/Project/Reward.php b/src/Goteo/Model/Project/Reward.php index 67df0d5954..dbc2eb7675 100644 --- a/src/Goteo/Model/Project/Reward.php +++ b/src/Goteo/Model/Project/Reward.php @@ -176,6 +176,75 @@ public static function getAll($project, $type = 'social', $lang = null, $fulfill } } + /** + * @return Reward[] + */ + public static function getSubscribableRewardsForProject(Project $project): array + { + $lang = Lang::current(); + list($fields, $joins) = self::getLangsSQLJoins($lang, $project->lang); + + $sql = "SELECT + reward.id as id, + reward.project as project, + $fields, + reward.type as type, + reward.icon as icon, + reward.license as license, + reward.amount as amount, + reward.units as units, + reward.fulsocial as fulsocial, + reward.url, + reward.bonus, + reward.category, + reward.subscribable + FROM reward + $joins + WHERE + reward.project = ? AND reward.subscribable = 1 + ORDER BY ISNULL(reward.amount) ASC, ISNULL(reward.reward) ASC, ISNULL(reward.description) ASC + "; + try { + $query = self::query($sql, [$project->id]); + return $query->fetchAll(\PDO::FETCH_CLASS, __CLASS__); + } catch (\PDOException $e) { + throw new ModelException($e->getMessage()); + } + } + + public static function getRewardsOrderBySubscribableForProject(Project $project): array + { + $lang = Lang::current(); + list($fields, $joins) = self::getLangsSQLJoins($lang, $project->lang); + + $sql = "SELECT + reward.id as id, + reward.project as project, + $fields, + reward.type as type, + reward.icon as icon, + reward.license as license, + reward.amount as amount, + reward.units as units, + reward.fulsocial as fulsocial, + reward.url, + reward.bonus, + reward.category, + reward.subscribable + FROM reward + $joins + WHERE + reward.project = ? + ORDER BY reward.subscribable DESC, reward.amount ASC + "; + try { + $query = self::query($sql, [$project->id]); + return $query->fetchAll(\PDO::FETCH_CLASS, __CLASS__); + } catch (\PDOException $e) { + throw new ModelException($e->getMessage()); + } + } + public static function getWidget($project, $lang = null) { if(empty($lang)) $lang = Lang::current(); diff --git a/src/routes.php b/src/routes.php index d72d7cacd8..97c162b4eb 100644 --- a/src/routes.php +++ b/src/routes.php @@ -307,4 +307,9 @@ ] )); +$routes->add('creator', new Route( + '/creator/{user_id}', + ['_controller' => 'Goteo\Controller\CreatorController::indexAction'] +)); + return $routes; diff --git a/translations/ca/general.yml b/translations/ca/general.yml index eb5d46ae5a..a026193b5e 100644 --- a/translations/ca/general.yml +++ b/translations/ca/general.yml @@ -260,4 +260,6 @@ regular-type: "Tipus" regular-source: "Font" regular-result-msg: "Missatge de resultat" regular-operation-type: "Operació de càlcul" - +regular-posts: "Publicacions" +regular-subscriptions: "Subscripcions" +regular-channels: "Canals" diff --git a/translations/en/general.yml b/translations/en/general.yml index c971cb71fd..4aba3380aa 100644 --- a/translations/en/general.yml +++ b/translations/en/general.yml @@ -266,3 +266,6 @@ regular-type: "Type" regular-source: "Source" regular-result-msg: "Result message" regular-operation-type: "Operation type" +regular-posts: "Posts" +regular-subscriptions: "Subscriptions" +regular-channels: "Channels" diff --git a/translations/es/general.yml b/translations/es/general.yml index b5a79fc30f..d62f6fbb84 100644 --- a/translations/es/general.yml +++ b/translations/es/general.yml @@ -282,3 +282,6 @@ regular-type: "Tipo" regular-source: "Fuente" regular-result-msg: "Mensaje de resultado" regular-operation-type: "Operación de cálculo" +regular-posts: "Publicaciones" +regular-subscriptions: "Suscripciones" +regular-channels: "Canales"