Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CategoriesStorageManager
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 4
110
0.00% covered (danger)
0.00%
0 / 1
 update
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 create
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 save
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 exists
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Storage manager for source and target categories in cx_corpora table. cx_corpora was initially
4 * used to store pairs of source and target (translated) sections, but extended to store pairs
5 * of source and target category lists, which utilize special section ID of 'CX_CATEGORY_METADATA'.
6 *
7 * @copyright See AUTHORS.txt
8 * @license GPL-2.0-or-later
9 */
10
11namespace ContentTranslation;
12
13use MediaWiki\MediaWikiServices;
14use Wikimedia\Rdbms\IDatabase;
15
16class CategoriesStorageManager {
17    private const TYPE_SOURCE = 'source';
18    private const TYPE_USER = 'user';
19
20    private const CATEGORIES_SECTION = [
21        'cxc_section_id' => 'CX_CATEGORY_METADATA'
22    ];
23
24    /**
25     * Update source or target categories. Since API doesn't require source nor target categories
26     * while performing saving of a draft, if $categories isn't provided, do nothing.
27     *
28     * @param IDatabase $db
29     * @param int $translationId
30     * @param string $categories
31     * @param string $origin TYPE_SOURCE or TYPE_USER. Defaults to TYPE_USER
32     */
33    private static function update(
34        IDatabase $db, $translationId, $categories, $origin = self::TYPE_USER
35    ) {
36        if ( !$categories ) {
37            return;
38        }
39
40        $values = [
41            'cxc_timestamp' => $db->timestamp(),
42            'cxc_content' => $categories
43        ];
44        $conditions = [
45            'cxc_translation_id' => $translationId,
46            'cxc_origin' => $origin
47        ] + self::CATEGORIES_SECTION;
48
49        $db->newUpdateQueryBuilder()
50            ->update( 'cx_corpora' )
51            ->set( $values )
52            ->where( $conditions )
53            ->caller( __METHOD__ )
54            ->execute();
55    }
56
57    /**
58     * Insert source and target category list for the given translation identifier.
59     *
60     * @param IDatabase $db
61     * @param int $translationId
62     * @param string $sourceCategories
63     * @param string $targetCategories
64     */
65    private static function create(
66        IDatabase $db, $translationId, $sourceCategories, $targetCategories
67    ) {
68        $values = [];
69        $commonValues = [
70            'cxc_translation_id' => $translationId,
71            'cxc_timestamp' => $db->timestamp()
72        ] + self::CATEGORIES_SECTION;
73
74        if ( $targetCategories ) {
75            $values[] = [
76                'cxc_origin' => self::TYPE_USER,
77                'cxc_content' => $targetCategories
78            ] + $commonValues;
79        }
80
81        if ( $sourceCategories ) {
82            $values[] = [
83                'cxc_origin' => self::TYPE_SOURCE,
84                'cxc_content' => $sourceCategories
85            ] + $commonValues;
86        }
87
88        if ( $values !== [] ) {
89            $db->newInsertQueryBuilder()
90                ->insertInto( 'cx_corpora' )
91                ->rows( $values )
92                ->caller( __METHOD__ )
93                ->execute();
94        }
95    }
96
97    /**
98     * Save the source and target category list.
99     * If the records exists, update them, otherwise create.
100     *
101     * @param int $translationId
102     * @param string $sourceCategories
103     * @param string $targetCategories
104     * @param bool $newTranslation Whether these are for a brand new Translation record
105     */
106    public static function save(
107        $translationId, $sourceCategories, $targetCategories, $newTranslation
108    ) {
109        /** @var LoadBalancer $lb */
110        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
111        $db = $lb->getConnection( DB_PRIMARY );
112
113        $db->doAtomicSection(
114            __METHOD__,
115            function ( IDatabase $db ) use (
116                $translationId, $sourceCategories, $targetCategories, $newTranslation
117            ) {
118                if ( $newTranslation ) {
119                    $existing = false;
120                } else {
121                    $existing = self::exists( $translationId );
122                }
123
124                if ( $existing ) {
125                    self::update( $db, $translationId, $sourceCategories, self::TYPE_SOURCE );
126                    self::update( $db, $translationId, $targetCategories );
127                } else {
128                    self::create( $db, $translationId, $sourceCategories, $targetCategories );
129                }
130            }
131        );
132    }
133
134    /**
135     * Find if there are records about source and target categories.
136     *
137     * @param int $translationId
138     * @return bool
139     */
140    private static function exists( $translationId ) {
141        /** @var LoadBalancer $lb */
142        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
143        $db = $lb->getConnection( DB_PRIMARY );
144
145        $result = $db->newSelectQueryBuilder()
146            ->select( 'cxc_content' )
147            ->from( 'cx_corpora' )
148            ->where( [ 'cxc_translation_id' => $translationId ] )
149            ->andWhere( self::CATEGORIES_SECTION )
150            ->forUpdate()
151            ->caller( __METHOD__ )
152            ->fetchResultSet();
153
154        return $result->numRows() > 0;
155    }
156}