turnstile

Drupal paywall plugin
Log | Files | Refs | README | LICENSE

TurnstilePriceCategory.php (9309B)


      1 <?php
      2 
      3 /**
      4  * @file
      5  * Price category structure for the GNU Taler Turnstile module.
      6  */
      7 
      8 namespace Drupal\taler_turnstile\Entity;
      9 
     10 use Drupal\Core\Config\Entity\ConfigEntityBase;
     11 use Drupal\Core\StringTranslation\StringTranslationTrait;
     12 
     13 /**
     14  * Defines the Price Category entity.
     15  *
     16  * @ConfigEntityType(
     17  *   id = "taler_turnstile_price_category",
     18  *   label = @Translation("Price Category"),
     19  *   handlers = {
     20  *     "list_builder" = "Drupal\taler_turnstile\PriceCategoryListBuilder",
     21  *     "form" = {
     22  *       "add" = "Drupal\taler_turnstile\Form\PriceCategoryForm",
     23  *       "edit" = "Drupal\taler_turnstile\Form\PriceCategoryForm",
     24  *       "delete" = "Drupal\taler_turnstile\Form\PriceCategoryDeleteForm"
     25  *     }
     26  *   },
     27  *   config_prefix = "taler_turnstile_price_category",
     28  *   admin_permission = "administer price categories",
     29  *   entity_keys = {
     30  *     "id" = "id",
     31  *     "label" = "label"
     32  *   },
     33  *   links = {
     34  *     "collection" = "/admin/structure/taler-turnstile-price-categories",
     35  *     "edit-form" = "/admin/structure/taler-turnstile-price-categories/{price_category}/edit",
     36  *     "delete-form" = "/admin/structure/taler-turnstile-price-categories/{price_category}/delete"
     37  *   },
     38  *   config_export = {
     39  *     "id",
     40  *     "label",
     41  *     "description",
     42  *     "prices"
     43  *   }
     44  * )
     45  */
     46 class TurnstilePriceCategory extends ConfigEntityBase {
     47 
     48   /**
     49    * For i18n, gives us the t() function.
     50    */
     51   use StringTranslationTrait;
     52 
     53   /**
     54    * The price category ID.
     55    *
     56    * @var string
     57    */
     58   protected $id;
     59 
     60   /**
     61    * The price category label.
     62    *
     63    * @var string
     64    */
     65   protected $label;
     66 
     67   /**
     68    * The price category description.
     69    *
     70    * @var string
     71    */
     72   protected $description;
     73 
     74   /**
     75    * The prices array.
     76    *
     77    * Structure: ['subscription_id' => ['currency_code' => 'price']]
     78    *
     79    * @var array
     80    */
     81   protected $prices = [];
     82 
     83   /**
     84    * Gets the description.
     85    *
     86    * @return string
     87    *   The description.
     88    */
     89   public function getDescription() {
     90     return $this->description;
     91   }
     92 
     93   /**
     94    * Gets a brief hint to display about non-subscriber prices.
     95    *
     96    * @return string
     97    *   A hint about the price
     98    */
     99   public function getPriceHint() {
    100     $prices = $this->getPrices();
    101     $nosub = $prices['%none%'];
    102     $rval = NULL;
    103     foreach ($nosub as $currency => $price) {
    104       if (NULL === $rval)
    105       {
    106         $rval = "$price $currency";
    107       }
    108       else
    109       {
    110         $rval = "$price $currency, " . $rval;
    111       }
    112     }
    113     return $rval ?? $this->t(/* Need to buy a subscription */ 'Not sold individually');
    114   }
    115 
    116   /**
    117    * Gets a brief hint to display about applicable subscriptions.
    118    *
    119    * @return string
    120    *   A hint about subscriptions
    121    */
    122   public function getSubscriptionHint() {
    123     $prices = $this->getPrices();
    124     $rval = NULL;
    125     foreach ($prices as $subscription => $details) {
    126       if ('%none%' === $subscription)
    127         continue;
    128       if (NULL === $rval)
    129       {
    130         $rval = $subscription;
    131       }
    132       else
    133       {
    134         $rval = $subscription . ", " . $rval;
    135       }
    136     }
    137     return $rval ?? $this->t(/* No subscriptions */ 'None');
    138   }
    139 
    140   /**
    141    * Gets all prices.
    142    *
    143    * @return array
    144    *   The prices array.
    145    */
    146   public function getPrices() {
    147     return $this->prices ?: [];
    148   }
    149 
    150   /**
    151    * Return the different subscriptions that this price category
    152    * has for which the resulting payment amount is zero (thus,
    153    * exlude subscriptions that would merely yield a discount).
    154    *
    155    * @return array
    156    *   The names (slugs) of the subscriptions
    157    *   that for this price category would yield a price of zero;
    158    *   note that empty prices do NOT count as zero but infinity!
    159    */
    160   public function getFullSubscriptions(): array {
    161     $subscriptions = [];
    162     foreach ($this->getPrices() as $tokenFamilySlug => $currencyMap) {
    163       foreach ($currencyMap as $currencyCode => $price) {
    164         if ( (is_numeric ($price)) &&
    165              (0.0 == (float) $price) ) {
    166           $subscriptions[] = $tokenFamilySlug;
    167           break;
    168         }
    169       }
    170     }
    171     return $subscriptions;
    172   }
    173 
    174   /**
    175    * Return the different payment choices in a way suitable
    176    * for GNU Taler v1 contracts.
    177    *
    178    * @param array $expirations
    179    *   Times when each possible subscription type expires.
    180    * @return array
    181    *    Structure suitable for the choices array in the v1 contract
    182    */
    183   public function getPaymentChoices(array $subscriptions): array {
    184     $max_cache = time() + 3600;
    185     $choices = [];
    186 
    187     foreach ($this->getPrices() as $tokenFamilySlug => $currencyMap) {
    188       if ("%none%" !== $tokenFamilySlug) {
    189         $subscription = $subscriptions[$tokenFamilySlug];
    190         $expi = $subscription['valid_before_s'] ?? 0;
    191         if ($expi < time()) {
    192             \Drupal::logger('taler_turnstile')->info('Subscription category @slug expired at @expire, skipping it.', ['@slug' => $tokenFamilySlug, '@expire' => $expi]);
    193             continue; // already expired
    194         }
    195         $max_cache = min ($max_cache,
    196                           $expi);
    197       }
    198       foreach ($currencyMap as $currencyCode => $price) {
    199         $inputs = [];
    200         if ("%none%" !== $tokenFamilySlug)
    201         {
    202           $inputs[] = [
    203             'type' => 'token',
    204             'token_family_slug' => $tokenFamilySlug,
    205             'count' => 1,
    206           ];
    207           $description = $this->t('Pay in @currency with subscription', [
    208             '@currency' => $currencyCode,
    209           ], [
    210             'langcode' => 'en', // force English version here!
    211           ]);
    212           $description_i18n = $this->buildTranslationMap (
    213             'Pay in @currency with subscription',
    214             ['@currency' => $currencyCode]
    215           );
    216           $choices[] = [
    217             'amount' => $currencyCode . ':' . $price,
    218             'description' => 'Pay in ' . $currencyCode . ' with subscription',
    219             // 'description_i18n' => $description_i18n,
    220             'inputs' => $inputs,
    221           ];
    222           $subscription_price = $this->getSubscriptionPrice ($tokenFamilySlug, $currencyCode);
    223           if ($subscription_price !== NULL) {
    224             // This subscription can be bought.
    225             $outputs = [];
    226             $outputs[] = [
    227               'type' => 'token',
    228               'token_family_slug' => $tokenFamilySlug,
    229               'count' => 1,
    230             ];
    231             $description = $this->t('Buy subscription in @currency', [
    232               '@currency' => $currencyCode,
    233             ]);
    234             $description_i18n = $this->buildTranslationMap (
    235               'Buy subscription in @currency',
    236               ['@currency' => $currencyCode]
    237             );
    238             $choices[] = [
    239               'amount' => $currencyCode . ':' . ((float) $subscription_price +
    240                                                  (float) $price),
    241               'description' => $description,
    242               'description_i18n' => $description_i18n,
    243               'outputs' => $outputs,
    244             ];
    245           }
    246         }
    247         else // case for no subscription
    248         {
    249           $description = $this->t('Pay in @currency', [
    250             '@currency' => $currencyCode,
    251           ]);
    252           $description_i18n = $this->buildTranslationMap (
    253             'Pay in @currency',
    254             ['@currency' => $currencyCode]
    255           );
    256           $choices[] = [
    257             'amount' => $currencyCode . ':' . (float) $price,
    258             'description' => $description,
    259             'description_i18n' => $description_i18n,
    260             'inputs' => $inputs,
    261           ];
    262         }
    263       } // for each possible currency
    264     } // for each type of subscription
    265 
    266     return $choices;
    267   }
    268 
    269   /**
    270    * Sets the prices array.
    271    *
    272    * @param array $prices
    273    *   The prices array.
    274    *
    275    * @return $this
    276    */
    277   public function setPrices(array $prices) {
    278     $this->prices = $prices;
    279     return $this;
    280   }
    281 
    282   /**
    283    * Determine the price of the given type of subscription
    284    * in the given currency.
    285    *
    286    * @param string $tokenFamilySlug
    287    *   The slug of the token family
    288    * @param string $currencyCode
    289    *   Currency code in which a price quote is desired
    290    *
    291    * @return string|null
    292    *   The subscription price (will map to a float), NULL on error
    293    */
    294   private function getSubscriptionPrice (string $tokenFamilySlug, string $currencyCode) {
    295     $config = \Drupal::config('taler_turnstile.settings');
    296     $subscriptions_prices = $config->get('subscription_prices') ?? [];
    297     $subscription_prices = $subscriptions_prices[$tokenFamilySlug] ?? [];
    298     $subscription_price = $subscription_prices[$currencyCode] ?? NULL;
    299     return $subscription_price;
    300   }
    301 
    302 
    303   /**
    304    * Build a translation map for all enabled languages.
    305    *
    306    * @param string $string
    307    *   The translatable string.
    308    * @param array $args
    309    *   Placeholder replacements.
    310    *
    311    * @return array
    312    *   Map of language codes to translated strings.
    313    */
    314   private function buildTranslationMap(string $string, array $args = []): array {
    315     $translations = [];
    316     $language_manager = \Drupal::languageManager();
    317 
    318     foreach ($language_manager->getLanguages() as $langcode => $language) {
    319       $translation = $this->t($string, $args, [
    320         'langcode' => $langcode,
    321       ]);
    322       $translations[$langcode] = (string) $translation;
    323     }
    324     return $translations;
    325   }
    326 
    327 }