sirjeff 4 settimane fa
parent
commit
053453c3a6

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+
+.git/
+
+inf.db
+

+ 45 - 0
Controller/ProjectBrandingController.php

@@ -0,0 +1,45 @@
+<?php namespace Kanboard\Plugin\OMIProjectBranding\Controller;
+
+use Kanboard\Controller\BaseController;
+
+/**
+ * ProjectBrandingController Controller
+ *
+ * @package  Kanboard\Plugin\OMITemplateModder
+ * @property \Kanboard\Plugin\OMIProjectBranding\Model\ProjectBrandingModel $projectBrandingModel
+ * @property \Kanboard\Plugin\OMIProjectBranding\Helper\ProjectBrandingHelper $projectBrandingHelper
+ * @author   Dwayne @ OMI NZ
+ */
+class ProjectBrandingController extends BaseController {
+  public function show() {
+    $project = $this->getProject();
+    $values = $this->request->getValues();
+    
+    if ($this->request->isPost()) {
+      $branding = [
+        "project_id" => $project["id"],
+        "logo_url" => $values["logo_url"],
+        "accent_color" => $values["accent_color"],
+        "enabled" => isset($values["enabled"]) ? 1 : 0,
+      ];
+      
+      if ($this->projectBrandingModel->saveBranding($branding)) {
+        $this->flash->success(t("Branding settings saved successfully."));
+      } else {
+        $this->flash->failure(t("Unable to save branding settings."));
+      }
+      
+      return $this->response->redirect($this->helper->url->to("ProjectBrandingController", "show", ["project_id" => $project["id"], "plugin" => "OMIProjectBranding"]), true);
+    }
+    
+    $branding = $this->projectBrandingModel->getBrandingByProjectId($project["id"]);
+    
+    $this->response->html($this->helper->layout->project("OMIProjectBranding:project_branding/show", [
+      "project" => $project,
+      "branding" => $branding,
+      "title" => t("Project Branding"),
+    ]));
+  }
+}
+#-
+#plugins/OMIProjectBranding/Controller/ProjectBrandingController.php

+ 22 - 0
Helper/ProjectBrandingHelper.php

@@ -0,0 +1,22 @@
+<?php namespace Kanboard\Plugin\OMIProjectBranding\Helper;
+
+use Kanboard\Core\Base;
+
+/**
+ * ProjectBrandingHelper Helper
+ *
+ * @author   Dwayne @ OMI NZ
+ */
+class ProjectBrandingHelper extends Base {
+  /**
+   * Get branding by project id
+   *
+   * @param  int  $projectId
+   * @return array|null
+   */
+  public function getBrandingByProjectId($projectId) {
+    return $this->projectBrandingModel->getBrandingByProjectId($projectId);
+  }    
+}
+#-
+#plugins/OMIProjectBranding/Helper/ProjectBrandingHelper.php

+ 32 - 0
Model/ProjectBrandingModel.php

@@ -0,0 +1,32 @@
+<?php namespace Kanboard\Plugin\OMIProjectBranding\Model;
+
+use Kanboard\Core\Base;
+/**
+ * ProjectBrandingModel Model
+ *
+ * @author   Dwayne @ OMI NZ
+ */
+class ProjectBrandingModel extends Base {
+  
+  const TABLE = "project_branding";
+  
+  public function getBrandingByProjectId($projectId) {
+    return $this->db->table(self::TABLE)
+        ->eq("project_id", $projectId)
+        ->findOne();
+  }
+  
+  public function saveBranding(array $branding) {
+    $brandingRecord = $this->getBrandingByProjectId($branding["project_id"]);
+  
+    if ($brandingRecord) {
+      return $this->db->table(self::TABLE)
+        ->eq("project_id", $branding["project_id"])
+        ->update($branding);
+    }
+    
+    return $this->db->table(self::TABLE)->insert($branding);
+  }
+}
+#-
+#plugins/OMIProjectBranding/Model/ProjectBrandingModel.php

+ 55 - 0
Plugin.php

@@ -0,0 +1,55 @@
+<?php namespace Kanboard\Plugin\OMIProjectBranding;
+
+use Kanboard\Core\Plugin\Base;
+use Kanboard\Core\Translator;
+
+/**
+ * Plugin Initialisation
+ *
+ * @package  Kanboard\Plugin\OMIProjectBranding
+ * @author   Dwayne @ OMI NZ
+ */
+class Plugin extends Base {
+  public function initialize() {
+    $this->container["helper"]->register("projectBrandingHelper", "\Kanboard\Plugin\OMIProjectBranding\Helper\ProjectBrandingHelper");
+    $this->hook->on("template:project:sidebar", ["template" => "OMIProjectBranding:project/sidebar"]);
+    $this->route->addRoute("project/:project_id/branding", "ProjectBrandingController", "show", "OMIProjectBranding");
+    $this->template->setTemplateOverride("header", "OMIProjectBranding:header");
+  }
+  
+  public function getClasses() {
+    return [
+      "Plugin\OMIProjectBranding\Controller" => [
+        "ProjectBrandingController",
+      ],
+      "Plugin\OMIProjectBranding\Model" => [
+        "ProjectBrandingModel",
+      ],
+      "Plugin\OMIProjectBranding\Helper" => [
+        "ProjectBrandingHelper",
+      ],
+    ];
+  }
+  
+  public function getPluginName() {
+    return "OMI Project Branding";
+  }
+  
+  public function getPluginDescription() {
+    return t("Project Branding plugin for Kanboard, to allow a custom logo and some brand colours per project.");
+  }
+  
+  public function getPluginAuthor() {
+    return "OMI NZ";
+  }
+  
+  public function getPluginVersion() {
+    return "1.0.1";
+  }
+  
+  public function getPluginHomepage() {
+    return "https://vcs.nz/ominz/OMIProjectBranding";
+  }
+}
+#-
+#plugins/OMIProjectBranding/Plugin.php

+ 32 - 0
Schema/Sqlite.php

@@ -0,0 +1,32 @@
+<?php namespace Kanboard\Plugin\OMIProjectBranding\Schema;
+
+use PDO;
+
+const VERSION = 2;
+
+function version_2(PDO $pdo) {
+  $pdo->exec('DROP TABLE project_branding;');
+  $pdo->exec('
+    CREATE TABLE IF NOT EXISTS project_branding (
+      id INTEGER PRIMARY KEY,
+      project_id INTEGER NOT NULL,
+      logo_url TEXT,
+      accent_color TEXT,
+      enabled INTEGER DEFAULT 0,
+      FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+    );
+  ');
+}
+
+function version_1(PDO $pdo) {
+  $pdo->exec('
+    CREATE TABLE IF NOT EXISTS project_branding (
+      id INTEGER PRIMARY KEY,
+      project_id INTEGER NOT NULL,
+      logo_url TEXT,
+      accent_color TEXT,
+      enabled INTEGER DEFAULT 0,
+      FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+    );
+  ');
+}

+ 6 - 0
Template/branding_hook.php

@@ -0,0 +1,6 @@
+<?php
+if (isset($project) && isset($this->projectBrandingHelper)) {
+  $branding = $this->projectBrandingHelper->getBranding($project["id"]);
+  $this->hook->attach("template:layout:head:after", "OMIProjectBranding:branding_style", ["branding" => $branding]);
+}
+?>

+ 7 - 0
Template/custom_style.php

@@ -0,0 +1,7 @@
+<?php if (isset($branding["enabled"]) && $branding["enabled"] == 1 && !empty($branding["accent_color"])): ?>
+  <style>
+    :root {
+      --kanboard-accent-color: <?= $branding["accent_color"] ?>;
+    }
+  </style>
+<?php endif ?>

+ 41 - 0
Template/header.php

@@ -0,0 +1,41 @@
+<?php
+
+$_title = $this->render("header/title", [
+  "project" => isset($project) ? $project : null,
+  "task" => isset($task) ? $task : null,
+  "description" => isset($description) ? $description : null,
+  "title" => $title,
+]);
+
+$_top_right_corner = implode("&nbsp;", [
+  $this->render("header/user_notifications"),
+  $this->render("header/creation_dropdown"),
+  $this->render("header/user_dropdown")
+]);
+
+$branding = null;
+if (isset($project) && isset($project["id"])) {
+  $branding = $this->projectBrandingHelper->getBrandingByProjectId($project["id"]);
+}
+?>
+
+<header>
+  <div class="title-container">
+    <?php if (isset($branding["enabled"]) && $branding["enabled"] == 1 && !empty($branding["logo_url"])): ?>
+      <a href="<?= $this->url->to('DashboardController', 'show') ?>"  style="border-bottom: 2px solid <?= $branding['accent_color'] ?>;">
+        <img src="<?= $branding['logo_url'] ?>" alt="<?= t('Logo') ?>" height="30">
+      </a>
+    <?php else: ?>
+        <?= $_title ?>
+    <?php endif ?>
+  </div>
+  <div class="board-selector-container">
+    <?php if (! empty($board_selector)): ?>
+        <?= $this->render("header/board_selector", ["board_selector" => $board_selector]) ?>
+    <?php endif ?>
+  </div>
+  <div class="menus-container">
+    <?= $_top_right_corner ?>
+  </div>
+</header>
+

+ 7 - 0
Template/header_vars.php

@@ -0,0 +1,7 @@
+<?php 
+// This file makes the helper available to the template
+if (!isset($projectBrandingHelper)) {
+  // If it's not set, try to get it from the container. This is the failsafe.
+  $projectBrandingHelper = $this->container["projectBrandingHelper"];
+}
+?>

+ 14 - 0
Template/project/branding_styles.php

@@ -0,0 +1,14 @@
+<?php if (isset($branding["enabled"]) && $branding["enabled"] == 1 && !empty($branding["accent_color"])): ?>
+<style>
+  .app-main .sidebar a.active, .btn-blue, .project-main .views-switcher a.active {
+    color: <?= $branding["accent_color"] ?> !important;
+  }
+  .btn-blue {
+    background-color: <?= $branding["accent_color"] ?> !important;
+    border-color: <?= $branding["accent_color"] ?> !important;
+  }
+  .project-header .views-switcher a.active::after, .view-project-header .views-switcher a.active::after {
+    background-color: <?= $branding["accent_color"] ?> !important;
+  }
+</style>
+<?php endif ?>

+ 3 - 0
Template/project/sidebar.php

@@ -0,0 +1,3 @@
+<li <?= $this->app->checkMenuSelection("ProjectBrandingController") ?>>
+    <?= $this->url->link(t("Branding"), "ProjectBrandingController", "show", ["project_id" => $project["id"], "plugin" => "OMIProjectBranding"]) ?>
+</li>

+ 24 - 0
Template/project_branding/show.php

@@ -0,0 +1,24 @@
+<div class="page-header">
+  <h2><?= t("Project Branding Settings") ?></h2>
+</div>
+
+<form method="post" action="<?= $this->url->to('ProjectBrandingController', 'show', ['project_id' => $project['id'], 'plugin' => 'OMIProjectBranding']) ?>" autocomplete="off">
+  <?= $this->form->csrf() ?>
+  
+  <div class="form-help">
+    <?= t("Configure the logo and primary color for this project.") ?>
+  </div>
+  
+  <?= $this->form->label(t("Enable custom branding"), "enabled") ?>
+  <?= $this->form->checkbox("enabled", "", 1, $branding["enabled"] ?? 0) ?>
+  
+  <?= $this->form->label(t("Logo URL"), "logo_url") ?>
+  <?= $this->form->text("logo_url", $branding, [], ['placeholder="https://example.com/logo.png"']) ?>
+  
+  <?= $this->form->label(t("Accent Color"), "accent_color") ?>
+  <?= $this->form->text("accent_color", $branding, [], ["type="color""]) ?>
+  
+  <div class="form-actions">
+    <button type="submit" class="btn btn-blue"><?= t("Save") ?></button>
+  </div>
+</form>

+ 99 - 0
Template/project_view/show.php

@@ -0,0 +1,99 @@
+<div class="page-header">
+  <h2><?= t("Summary") ?></h2>
+</div>
+<ul class="panel">
+    <li><strong><?= $project["is_active"] ? t("This project is open") : t("This project is closed") ?></strong></li>
+
+  <?php if ($project["owner_id"] > 0): ?>
+    <li><?= t('Project owner: ') ?><strong><?= $this->text->e($project['owner_name'] ?: $project['owner_username']) ?></strong></li>
+  <?php endif ?>
+
+  <?php if ($project["is_private"]): ?>
+    <li><i class="fa fa-lock"></i> <?= t("This project is personal") ?></li>
+  <?php endif ?>
+
+  <?php if ($project['is_public']): ?>
+    <li><?= $this->url->icon("share-alt", t("Public link"), "BoardViewController", "readonly", array("token" => $project["token"]), false, "", "", true) ?></li>
+    <li><?= $this->url->icon("rss-square", t("RSS feed"), "FeedController", "project", array("token" => $project["token"]), false, "", "", true) ?></li>
+    <li><?= $this->url->icon("calendar", t("iCal feed"), "ICalendarController", "project", array("token" => $project["token"])) ?></li>
+  <?php else: ?>
+    <li><?= t("Public access disabled") ?></li>
+  <?php endif ?>
+
+  <?php if ($project["last_modified"]): ?>
+    <li><?= t("Modified:")." ".$this->dt->datetime($project["last_modified"]) ?></li>
+  <?php endif ?>
+
+  <?php if ($project["start_date"]): ?>
+    <li><?= t("Start date: ").$this->dt->date($project["start_date"]) ?></li>
+  <?php endif ?>
+
+  <?php if ($project["end_date"]): ?>
+    <li><?= t("End date: ").$this->dt->date($project["end_date"]) ?></li>
+  <?php endif ?>
+
+  <?php if ($project["per_swimlane_task_limits"]): ?>
+    <li><?= t("Column task limits are applied to each swimlane individually") ?></li>
+  <?php else: ?>
+    <li><?= t("Column task limits are applied across swimlanes") ?></li>
+  <?php endif ?>
+
+    <li><?= t("Task limit: ") ?><?= $project["task_limit"] ? $project["task_limit"] : "infinite" ?></li>
+</ul>
+
+<?php if (! empty($project["description"])): ?>
+  <div class="page-header">
+    <h2><?= t("Description") ?></h2>
+  </div>
+  
+  <?php if (isset($values) && isset($errors)): ?>
+  <?= $this->hook->render("template:project:view:form", array("values" => $values, "errors" => $errors)) ?>
+  <?php endif ?>
+  
+  <article class="markdown">
+    <?= $this->text->markdown($project["description"]) ?>
+  </article>
+<?php endif ?>
+
+<div class="page-header">
+  <h2><?= t("Columns") ?></h2>
+</div>
+<?php if (empty($columns)): ?>
+  <p class="alert alert-error"><?= t("Your board doesn't have any columns!") ?></p>
+<?php else: ?>
+  <table class="table-striped table-scrolling">
+  <thead>
+  <tr>
+    <th class="column-40"><?= t("Column") ?></th>
+    <th class="column-10"><?= t("Task limit") ?></th>
+    <th class="column-20"><?= t("Visible on dashboard") ?></th>
+    <th class="column-15"><?= t("Open tasks") ?></th>
+    <th class="column-15"><?= t("Closed tasks") ?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <?php foreach ($columns as $column): ?>
+    <tr data-column-id="<?= $column["id"] ?>">
+      <td>
+        <?= $this->text->e($column["title"]) ?>
+        <?php if (! empty($column["description"])): ?>
+          <?= $this->app->tooltipMarkdown($column["description"]) ?>
+        <?php endif ?>
+      </td>
+      <td>
+        <?= $column["task_limit"] ?: "infinite" ?>
+      </td>
+      <td>
+        <?= $column["hide_in_dashboard"] == 0 ? t("Yes") : t("No") ?>
+      </td>
+      <td>
+        <?= $column["nb_open_tasks"] ?>
+      </td>
+      <td>
+        <?= $column["nb_closed_tasks"] ?>
+      </td>
+    </tr>
+  <?php endforeach ?>
+  </tbody>
+  </table>
+<?php endif ?>

+ 8 - 0
release.md

@@ -0,0 +1,8 @@
+# Release Notes 
+
+## v1.0.1 2025-08-12 *[main]*
+
+- New initial build  
+  
+Built by Dwayne @ OMI NZ   
+

+ 1 - 0
ver

@@ -0,0 +1 @@
+1.0.1